From 19a7343c8068ba288e90292879ce6d2338896a51 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 5 Jul 2026 21:02:05 +0300 Subject: [PATCH] feat(properties): collapse the panel to a vertical bar on a narrow window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the window is too narrow for the full properties panel it now collapses to a slim vertical "Properties" bar (its label drawn rotated 90° on a canvas), freeing width for the viewport. Clicking the bar expands the panel back out alongside the bar; clicking it again re-collapses (props_expanded state + TogglePropertiesBar). Reuses the rotated-label bar helper originally built for the Start page. Co-Authored-By: Claude Opus 4.8 --- src/app/mod.rs | 6 +++ src/app/update/mod.rs | 5 +++ src/app/view/mod.rs | 100 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 803275599..a143b732f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -232,6 +232,9 @@ pub(super) struct OpenCADStudio { /// Which Start-page section is shown when the page is too narrow for all /// three side by side and falls back to a tab bar. start_section: StartSection, + /// When the window is too narrow the properties panel collapses to a + /// vertical bar; this is the user's toggle to expand it back out. + props_expanded: bool, /// Read-only editor buffer backing the command-line history dropdown, so /// the log can be drag-selected across lines and copied (issue #232). /// Rebuilt from the history each time the dropdown is opened. @@ -1129,6 +1132,8 @@ pub enum Message { OpenUrl(String), /// Select which section a narrow (tabbed) Start page shows. StartSectionSelect(StartSection), + /// Expand/collapse the properties panel when it has shrunk to a bar. + TogglePropertiesBar, /// Scroll the status-bar layout-tab strip horizontally by `delta` px /// (negative = left). Driven by the ‹ › arrows next to the tabs. ScrollLayoutTabs(f32), @@ -1975,6 +1980,7 @@ impl OpenCADStudio { command_line: CommandLine::new(), patrons: Vec::new(), start_section: StartSection::default(), + props_expanded: false, history_content: iced::widget::text_editor::Content::new(), status_bar: StatusBar::new(), cursor_pos: Point::ORIGIN, diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 5e9004816..af3eecdaa 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -226,6 +226,11 @@ impl OpenCADStudio { Task::none() } + Message::TogglePropertiesBar => { + self.props_expanded = !self.props_expanded; + Task::none() + } + Message::ScrollLayoutTabs(dx) => iced::widget::operation::scroll_by( iced::advanced::widget::Id::new(crate::ui::statusbar::LAYOUT_TABS_SCROLL_ID), iced::widget::scrollable::AbsoluteOffset { x: dx, y: 0.0 }, diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 6a3730811..b8c24c506 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -8,8 +8,8 @@ use crate::scene::view::viewport_pane::ViewportPane; use crate::scene::{VIEWCUBE_PAD, VIEWCUBE_REGION_PX}; use crate::ui::wrap_bar::WrapFlow; use iced::widget::{ - button, column, container, mouse_area, pane_grid, responsive, row, shader, stack, text, Row, - Space, + button, canvas, column, container, mouse_area, pane_grid, responsive, row, shader, stack, text, + Row, Space, }; use iced::window; use iced::{keyboard, Background, Border, Color, Element, Fill, Subscription, Task, Theme}; @@ -1143,7 +1143,17 @@ impl OpenCADStudio { let properties_el: Element<'_, Message> = if tab.is_start { Space::new().into() } else if self.show_properties && !self.clean_screen { - tab.properties.view() + // On a narrow window the panel collapses to a vertical "Properties" + // bar to free width for the viewport; clicking the bar expands it. + let narrow = self.win_size.0 < 1000.0; + let bar = || collapse_bar("Properties", Message::TogglePropertiesBar); + if narrow && !self.props_expanded { + bar() + } else if narrow { + row![bar(), tab.properties.view()].into() + } else { + tab.properties.view() + } } else { Space::new().into() }; @@ -1987,6 +1997,90 @@ fn pane_mouse_area<'a>(idx: usize) -> Element<'a, Message> { .into() } +/// Canvas that draws a label rotated 90° (for a collapsed panel's bar). +struct VBarLabel { + text: String, + color: Color, +} + +impl canvas::Program for VBarLabel { + type State = (); + + fn draw( + &self, + _state: &(), + renderer: &iced::Renderer, + _theme: &Theme, + bounds: iced::Rectangle, + _cursor: iced::advanced::mouse::Cursor, + ) -> Vec { + let mut frame = canvas::Frame::new(renderer, bounds.size()); + frame.with_save(|frame| { + frame.translate(iced::Vector::new(bounds.width / 2.0, bounds.height / 2.0)); + frame.rotate(iced::Radians(std::f32::consts::FRAC_PI_2)); + frame.fill_text(canvas::Text { + content: self.text.clone(), + position: iced::Point::ORIGIN, + color: self.color, + size: iced::Pixels(13.0), + align_x: iced::advanced::text::Alignment::Center, + align_y: iced::alignment::Vertical::Center, + shaping: iced::advanced::text::Shaping::Advanced, + ..Default::default() + }); + }); + vec![frame.into_geometry()] + } +} + +/// A collapsed panel rendered as a tall narrow bar with its name written along +/// it, rotated 90°. Pressing it emits `on_press`. +pub(super) fn collapse_bar<'a>(name: &str, on_press: Message) -> Element<'a, Message> { + const LABEL: Color = Color { + r: 0.72, + g: 0.72, + b: 0.74, + a: 1.0, + }; + const BAR_BG: Color = Color { + r: 0.13, + g: 0.13, + b: 0.14, + a: 1.0, + }; + const BAR_BORDER: Color = Color { + r: 0.22, + g: 0.22, + b: 0.24, + a: 1.0, + }; + + let label = canvas(VBarLabel { + text: name.to_string(), + color: LABEL, + }) + .width(Fill) + .height(Fill); + + mouse_area( + container(label) + .width(iced::Length::Fixed(26.0)) + .height(Fill) + .style(|_: &Theme| container::Style { + background: Some(Background::Color(BAR_BG)), + border: Border { + color: BAR_BORDER, + width: 1.0, + radius: 0.0.into(), + }, + ..Default::default() + }), + ) + .interaction(iced::mouse::Interaction::Pointer) + .on_press(on_press) + .into() +} + pub(super) fn start_page_view<'a>( patrons: &'a [(String, i64)], recents: &'a [std::path::PathBuf],