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 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-05 21:02:05 +03:00
commit 19a7343c80
3 changed files with 108 additions and 3 deletions

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],