feat(properties): collapse the panel to a vertical bar on a narrow window

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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-05 21:02:05 +03:00
1 parent e2a56033d4
commit 19a7343c80
3 files changed
+108 -3

No files matched your search

+6
View File
@@ -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,
+5
View File
@@ -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 },
+97 -3
View File
@@ -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<Message> for VBarLabel {
type State = ();
fn draw(
&self,
_state: &(),
renderer: &iced::Renderer,
_theme: &Theme,
bounds: iced::Rectangle,
_cursor: iced::advanced::mouse::Cursor,
) -> Vec<canvas::Geometry> {
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],