forked from 1b-agent/opencadstudio-upstream
feat(ui): add dockable properties panel
Preserve panel width across left/right docking and persist resize, position, and auto-collapse preferences. Refs #348 Refs #650
This commit is contained in:
1 parent
e35348525a
commit
4be8d9735c
8 files changed
+355
-27
No files matched your search
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3 H16 L15 9 L18 12 V14 H6 V12 L9 9 Z"/><path d="M12 14 V21"/></svg>
|
||||
|
After Width: | Height: | Size: 235 B |
@@ -27,6 +27,8 @@ pub struct AppConfig {
|
||||
pub start: StartConfig,
|
||||
/// Which status-bar pills the user has hidden.
|
||||
pub statusbar: StatusBarConfig,
|
||||
/// Dock position, width and auto-collapse behavior of the Properties panel.
|
||||
pub properties: PropertiesDockConfig,
|
||||
/// Add a newly selected annotation scale to existing annotative objects.
|
||||
pub annotation_auto_scale: i8,
|
||||
/// Ribbon collapse density.
|
||||
@@ -44,6 +46,7 @@ impl Default for AppConfig {
|
||||
recent: RecentConfig::default(),
|
||||
start: StartConfig::default(),
|
||||
statusbar: StatusBarConfig::default(),
|
||||
properties: PropertiesDockConfig::default(),
|
||||
annotation_auto_scale: -4,
|
||||
ribbon: RibbonConfig::default(),
|
||||
plot: PlotDialogState::default(),
|
||||
@@ -51,6 +54,30 @@ impl Default for AppConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DockSide {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct PropertiesDockConfig {
|
||||
pub side: DockSide,
|
||||
pub width: f32,
|
||||
pub auto_collapse: bool,
|
||||
}
|
||||
|
||||
impl Default for PropertiesDockConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
side: DockSide::Left,
|
||||
width: 250.0,
|
||||
auto_collapse: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct UiThemeConfig {
|
||||
|
||||
@@ -305,6 +305,16 @@ pub(super) struct OpenCADStudio {
|
||||
/// 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,
|
||||
/// Persisted dock width and side of the Properties panel.
|
||||
properties_width: f32,
|
||||
properties_side: config::DockSide,
|
||||
properties_auto_collapse: bool,
|
||||
/// Transient hover/drag state for the docked Properties panel.
|
||||
properties_hovered: bool,
|
||||
properties_dragging: bool,
|
||||
properties_resizing: bool,
|
||||
properties_drag_last: Option<Point>,
|
||||
properties_dock_preview: Option<config::DockSide>,
|
||||
/// 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.
|
||||
@@ -1620,6 +1630,21 @@ pub enum Message {
|
||||
StartSectionSelect(StartSection),
|
||||
/// Expand/collapse the properties panel when it has shrunk to a bar.
|
||||
TogglePropertiesBar,
|
||||
/// Close the docked Properties panel; the ribbon command can reopen it.
|
||||
PropertiesClose,
|
||||
/// Pin/unpin the docked Properties panel.
|
||||
PropertiesAutoCollapseToggle,
|
||||
/// Hover state drives expansion while auto-collapse is enabled.
|
||||
PropertiesHover(bool),
|
||||
/// Begin dragging the Properties title bar to the opposite dock edge.
|
||||
PropertiesDockGrab,
|
||||
/// Begin dragging the Properties/viewport divider.
|
||||
PropertiesResizeGrab,
|
||||
/// Reset the dock width to its default value.
|
||||
PropertiesWidthReset,
|
||||
/// Full-workspace pointer tracking shared by dock and resize drags.
|
||||
PropertiesDragMove(Point),
|
||||
PropertiesDragRelease,
|
||||
/// Scroll the status-bar layout-tab strip horizontally by `delta` px
|
||||
/// (negative = left). Driven by the ‹ › arrows next to the tabs.
|
||||
ScrollLayoutTabs(f32),
|
||||
@@ -2790,6 +2815,14 @@ impl OpenCADStudio {
|
||||
start_section: StartSection::default(),
|
||||
start_action_w: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)),
|
||||
props_expanded: false,
|
||||
properties_width: 250.0,
|
||||
properties_side: config::DockSide::Left,
|
||||
properties_auto_collapse: false,
|
||||
properties_hovered: false,
|
||||
properties_dragging: false,
|
||||
properties_resizing: false,
|
||||
properties_drag_last: None,
|
||||
properties_dock_preview: None,
|
||||
history_content: iced::widget::text_editor::Content::new(),
|
||||
status_bar: StatusBar::new(),
|
||||
cursor_pos: Point::ORIGIN,
|
||||
|
||||
@@ -520,6 +520,11 @@ impl OpenCADStudio {
|
||||
section: self.start_section,
|
||||
},
|
||||
statusbar: self.statusbar_config.clone(),
|
||||
properties: crate::app::config::PropertiesDockConfig {
|
||||
side: self.properties_side,
|
||||
width: self.properties_width,
|
||||
auto_collapse: self.properties_auto_collapse,
|
||||
},
|
||||
annotation_auto_scale: self.annotation_auto_scale,
|
||||
ribbon: crate::app::config::RibbonConfig {
|
||||
collapse: self.ribbon.collapse_mode(),
|
||||
@@ -550,6 +555,13 @@ impl OpenCADStudio {
|
||||
// (`refresh_recent_thumbs`) — never here on the boot path.
|
||||
self.start_section = cfg.start.section;
|
||||
self.statusbar_config = cfg.statusbar;
|
||||
self.properties_side = cfg.properties.side;
|
||||
self.properties_width = if cfg.properties.width.is_finite() {
|
||||
cfg.properties.width.clamp(220.0, 600.0)
|
||||
} else {
|
||||
250.0
|
||||
};
|
||||
self.properties_auto_collapse = cfg.properties.auto_collapse;
|
||||
self.annotation_auto_scale = cfg.annotation_auto_scale.clamp(-4, 4);
|
||||
self.ribbon.set_collapse_mode(cfg.ribbon.collapse);
|
||||
self.plot_dialog = cfg.plot;
|
||||
|
||||
@@ -350,6 +350,110 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesClose => {
|
||||
self.show_properties = false;
|
||||
self.properties_hovered = false;
|
||||
self.properties_dragging = false;
|
||||
self.properties_resizing = false;
|
||||
self.properties_drag_last = None;
|
||||
self.properties_dock_preview = None;
|
||||
self.ribbon.set_properties(false);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesAutoCollapseToggle => {
|
||||
self.properties_auto_collapse ^= true;
|
||||
// Keep the panel open while the pointer is still over the
|
||||
// button; leaving the panel performs the first collapse.
|
||||
self.properties_hovered = self.properties_auto_collapse;
|
||||
self.save_config();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesHover(hovered) => {
|
||||
if self.properties_auto_collapse
|
||||
&& !self.properties_dragging
|
||||
&& !self.properties_resizing
|
||||
{
|
||||
self.properties_hovered = hovered;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesDockGrab => {
|
||||
self.properties_dragging = true;
|
||||
self.properties_resizing = false;
|
||||
self.properties_drag_last = None;
|
||||
self.properties_dock_preview = Some(self.properties_side);
|
||||
self.properties_hovered = true;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesResizeGrab => {
|
||||
self.properties_resizing = true;
|
||||
self.properties_dragging = false;
|
||||
self.properties_drag_last = None;
|
||||
self.properties_dock_preview = None;
|
||||
self.properties_hovered = true;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesWidthReset => {
|
||||
self.properties_width = 250.0;
|
||||
self.save_config();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesDragMove(point) => {
|
||||
if self.properties_dragging {
|
||||
self.properties_dock_preview = Some(if point.x < self.win_size.0 * 0.5 {
|
||||
crate::app::config::DockSide::Left
|
||||
} else {
|
||||
crate::app::config::DockSide::Right
|
||||
});
|
||||
} else if self.properties_resizing {
|
||||
if let Some(last) = self.properties_drag_last {
|
||||
let dx = point.x - last.x;
|
||||
let delta = match self.properties_side {
|
||||
crate::app::config::DockSide::Left => dx,
|
||||
crate::app::config::DockSide::Right => -dx,
|
||||
};
|
||||
let max_width = (self.win_size.0 * 0.45).clamp(220.0, 600.0);
|
||||
self.properties_width =
|
||||
(self.properties_width + delta).clamp(220.0, max_width);
|
||||
}
|
||||
}
|
||||
if self.properties_dragging || self.properties_resizing {
|
||||
self.properties_drag_last = Some(point);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropertiesDragRelease => {
|
||||
let changed = if self.properties_dragging {
|
||||
if let Some(side) = self.properties_dock_preview {
|
||||
let changed = side != self.properties_side;
|
||||
self.properties_side = side;
|
||||
changed
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.properties_resizing
|
||||
};
|
||||
self.properties_dragging = false;
|
||||
self.properties_resizing = false;
|
||||
self.properties_drag_last = None;
|
||||
self.properties_dock_preview = None;
|
||||
if self.properties_auto_collapse {
|
||||
self.properties_hovered = false;
|
||||
}
|
||||
if changed {
|
||||
self.save_config();
|
||||
}
|
||||
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 },
|
||||
|
||||
+124
-23
@@ -12,7 +12,7 @@ use iced::widget::{
|
||||
stack, text, Row, Space,
|
||||
};
|
||||
use iced::window;
|
||||
use iced::{keyboard, Background, Border, Color, Element, Fill, Subscription, Task, Theme};
|
||||
use iced::{keyboard, Background, Border, Color, Element, Fill, Length, Subscription, Task, Theme};
|
||||
use iced_aw::ContextMenu;
|
||||
use crate::t;
|
||||
|
||||
@@ -1342,26 +1342,42 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// The Start tab shows its own three-panel page (Recent · Welcome ·
|
||||
// Supporters) in place of the viewport, so the left properties slot is
|
||||
// empty there.
|
||||
let properties_el: Element<'_, Message> = if tab.is_start {
|
||||
Space::new().into()
|
||||
} else if self.show_properties && !self.clean_screen {
|
||||
// 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()
|
||||
// Docked Properties panel. It keeps its pixel width when moved between
|
||||
// edges; auto-collapse swaps the full panel for a hoverable rail.
|
||||
let show_properties = !tab.is_start && self.show_properties && !self.clean_screen;
|
||||
let properties_width = self
|
||||
.properties_width
|
||||
.min((self.win_size.0 * 0.45).clamp(220.0, 600.0));
|
||||
let properties_el: Option<Element<'_, Message>> = show_properties.then(|| {
|
||||
let narrow_collapsed = self.win_size.0 < 1000.0
|
||||
&& !self.props_expanded
|
||||
&& !self.properties_hovered;
|
||||
let auto_collapsed = self.properties_auto_collapse
|
||||
&& !self.properties_hovered
|
||||
&& !self.properties_dragging
|
||||
&& !self.properties_resizing;
|
||||
if narrow_collapsed || auto_collapsed {
|
||||
collapse_bar(
|
||||
"Properties",
|
||||
self.properties_side,
|
||||
Message::TogglePropertiesBar,
|
||||
Message::PropertiesHover(true),
|
||||
)
|
||||
} else {
|
||||
tab.properties.view()
|
||||
let panel = tab
|
||||
.properties
|
||||
.view(properties_width, self.properties_auto_collapse);
|
||||
let divider = properties_divider();
|
||||
let group: Element<'_, Message> = match self.properties_side {
|
||||
crate::app::config::DockSide::Left => row![panel, divider].into(),
|
||||
crate::app::config::DockSide::Right => row![divider, panel].into(),
|
||||
};
|
||||
mouse_area(group)
|
||||
.on_enter(Message::PropertiesHover(true))
|
||||
.on_exit(Message::PropertiesHover(false))
|
||||
.into()
|
||||
}
|
||||
} else {
|
||||
Space::new().into()
|
||||
};
|
||||
});
|
||||
|
||||
// Drawing viewports keep the command line as a bottom-centre overlay so
|
||||
// the input stays close to the cursor. The Start page gives it a real
|
||||
@@ -1378,7 +1394,60 @@ impl OpenCADStudio {
|
||||
(self.dyn_input && tab.active_cmd.is_some() && !tab.dyn_fields.is_empty())
|
||||
|| self.mtext_editor.as_ref().is_some_and(|e| e.show_preview)
|
||||
|| self.text_inline.is_some();
|
||||
let workspace = row![properties_el, viewport_stack].width(Fill).height(Fill);
|
||||
let workspace: Element<'_, Message> = match (properties_el, self.properties_side) {
|
||||
(Some(properties), crate::app::config::DockSide::Left) => {
|
||||
row![properties, viewport_stack].width(Fill).height(Fill).into()
|
||||
}
|
||||
(Some(properties), crate::app::config::DockSide::Right) => {
|
||||
row![viewport_stack, properties].width(Fill).height(Fill).into()
|
||||
}
|
||||
(None, _) => container(viewport_stack).width(Fill).height(Fill).into(),
|
||||
};
|
||||
let workspace: Element<'_, Message> = if self.properties_dragging {
|
||||
let preview_side = self.properties_dock_preview.unwrap_or(self.properties_side);
|
||||
let preview = container(Space::new())
|
||||
.width(Length::Fixed(properties_width))
|
||||
.height(Fill)
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(
|
||||
palette.primary.weak.color.scale_alpha(0.72),
|
||||
)),
|
||||
border: Border {
|
||||
color: palette.primary.base.color,
|
||||
width: 2.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
let preview = container(preview)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(match preview_side {
|
||||
crate::app::config::DockSide::Left => iced::alignment::Horizontal::Left,
|
||||
crate::app::config::DockSide::Right => iced::alignment::Horizontal::Right,
|
||||
});
|
||||
stack![workspace, preview].width(Fill).height(Fill).into()
|
||||
} else {
|
||||
workspace
|
||||
};
|
||||
let workspace: Element<'_, Message> = if self.properties_dragging
|
||||
|| self.properties_resizing
|
||||
{
|
||||
mouse_area(workspace)
|
||||
.on_move(Message::PropertiesDragMove)
|
||||
.on_release(Message::PropertiesDragRelease)
|
||||
.interaction(if self.properties_resizing {
|
||||
iced::mouse::Interaction::ResizingHorizontally
|
||||
} else {
|
||||
iced::mouse::Interaction::Grabbing
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
workspace
|
||||
};
|
||||
let command_line = self.command_line.view(
|
||||
allow_autocomplete,
|
||||
dyn_capturing,
|
||||
@@ -2200,6 +2269,7 @@ fn pane_mouse_area<'a>(idx: usize) -> Element<'a, Message> {
|
||||
/// Canvas that draws a label rotated 90° (for a collapsed panel's bar).
|
||||
struct VBarLabel {
|
||||
text: String,
|
||||
clockwise: bool,
|
||||
}
|
||||
|
||||
impl canvas::Program<Message> for VBarLabel {
|
||||
@@ -2216,7 +2286,11 @@ impl canvas::Program<Message> for VBarLabel {
|
||||
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.rotate(iced::Radians(if self.clockwise {
|
||||
std::f32::consts::FRAC_PI_2
|
||||
} else {
|
||||
-std::f32::consts::FRAC_PI_2
|
||||
}));
|
||||
frame.fill_text(canvas::Text {
|
||||
content: self.text.clone(),
|
||||
position: iced::Point::ORIGIN,
|
||||
@@ -2233,10 +2307,17 @@ impl canvas::Program<Message> for VBarLabel {
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
/// it. It can be clicked on narrow windows or expanded by hover when auto-hide
|
||||
/// is enabled.
|
||||
pub(super) fn collapse_bar<'a>(
|
||||
name: &str,
|
||||
side: crate::app::config::DockSide,
|
||||
on_press: Message,
|
||||
on_enter: Message,
|
||||
) -> Element<'a, Message> {
|
||||
let label = canvas(VBarLabel {
|
||||
text: name.to_string(),
|
||||
clockwise: side == crate::app::config::DockSide::Left,
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
@@ -2259,9 +2340,29 @@ pub(super) fn collapse_bar<'a>(name: &str, on_press: Message) -> Element<'a, Mes
|
||||
)
|
||||
.interaction(iced::mouse::Interaction::Pointer)
|
||||
.on_press(on_press)
|
||||
.on_enter(on_enter)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Grabbable separator between the docked panel and drawing view. The visible
|
||||
/// line is wider than a single pixel so it remains discoverable in every theme.
|
||||
fn properties_divider() -> Element<'static, Message> {
|
||||
let line = container(Space::new())
|
||||
.width(Length::Fixed(5.0))
|
||||
.height(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
});
|
||||
mouse_area(line)
|
||||
.on_press(Message::PropertiesResizeGrab)
|
||||
.on_double_click(Message::PropertiesWidthReset)
|
||||
.interaction(iced::mouse::Interaction::ResizingHorizontally)
|
||||
.into()
|
||||
}
|
||||
|
||||
const START_ACTION_RADIUS: f32 = 6.0;
|
||||
|
||||
fn start_action_shape(mut style: button::Style) -> button::Style {
|
||||
|
||||
@@ -64,6 +64,7 @@ pub const COPY: &[u8] = include_bytes!("../../assets/icons/ui/copy.svg");
|
||||
pub const MENU: &[u8] = include_bytes!("../../assets/icons/ui/menu.svg");
|
||||
pub const MOVE: &[u8] = include_bytes!("../../assets/icons/ui/move.svg");
|
||||
pub const RESIZE: &[u8] = include_bytes!("../../assets/icons/ui/resize.svg");
|
||||
pub const PIN: &[u8] = include_bytes!("../../assets/icons/ui/pin.svg");
|
||||
pub const SPLIT_V: &[u8] = include_bytes!("../../assets/icons/ui/split_v.svg");
|
||||
pub const SPLIT_H: &[u8] = include_bytes!("../../assets/icons/ui/split_h.svg");
|
||||
pub const GRID: &[u8] = include_bytes!("../../assets/icons/ui/grid.svg");
|
||||
|
||||
+53
-4
@@ -15,6 +15,7 @@ use acadrust::types::{Color as AcadColor, LineWeight};
|
||||
use acadrust::Handle;
|
||||
use iced::widget::{
|
||||
button, canvas, column, combo_box, container, mouse_area, row, scrollable, text, text_input,
|
||||
tooltip, Space,
|
||||
};
|
||||
use iced::{
|
||||
mouse, Background, Border, Color, Element, Length, Padding, Point, Rectangle, Size, Theme,
|
||||
@@ -367,9 +368,54 @@ impl PropertiesPanel {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
pub fn view(&self, width: f32, auto_collapse: bool) -> Element<'_, Message> {
|
||||
// ── Header ──────────────────────────────────────────────────────────
|
||||
let header = container(text(t!("Properties")).size(12))
|
||||
let pin_icon = if auto_collapse {
|
||||
crate::ui::icons::themed_primary_weak_text(crate::ui::icons::PIN, 12.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::PIN, 12.0)
|
||||
};
|
||||
let pin = button(pin_icon)
|
||||
.on_press(Message::PropertiesAutoCollapseToggle)
|
||||
.style(move |theme: &Theme, status| {
|
||||
let mut style = button::subtle(theme, status);
|
||||
if auto_collapse {
|
||||
let palette = theme.palette();
|
||||
style.background = Some(Background::Color(palette.primary.weak.color));
|
||||
style.text_color = palette.primary.weak.text;
|
||||
style.border.color = palette.primary.base.color;
|
||||
style.border.width = 1.0;
|
||||
}
|
||||
style
|
||||
})
|
||||
.padding([3, 5]);
|
||||
let pin = tooltip(pin, text(t!("Auto")).size(10), tooltip::Position::Bottom).gap(4);
|
||||
|
||||
let close = button(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::CLOSE,
|
||||
12.0,
|
||||
))
|
||||
.on_press(Message::PropertiesClose)
|
||||
.style(button::subtle)
|
||||
.padding([3, 5]);
|
||||
let close = tooltip(
|
||||
close,
|
||||
text(t!("Close")).size(10),
|
||||
tooltip::Position::Bottom,
|
||||
)
|
||||
.gap(4);
|
||||
|
||||
let header = mouse_area(
|
||||
container(
|
||||
row![
|
||||
text(t!("Properties")).size(12),
|
||||
Space::new().width(Length::Fill),
|
||||
pin,
|
||||
close,
|
||||
]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weak.color,
|
||||
@@ -377,7 +423,10 @@ impl PropertiesPanel {
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.padding([6, 10]);
|
||||
.padding([3, 6]),
|
||||
)
|
||||
.on_press(Message::PropertiesDockGrab)
|
||||
.interaction(iced::mouse::Interaction::Grab);
|
||||
|
||||
// ── Title bar (entity type / "No Selection") ─────────────────────
|
||||
let title_content: Element<'_, Message> = if self.selection_groups.is_empty() {
|
||||
@@ -446,7 +495,7 @@ impl PropertiesPanel {
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(250)
|
||||
.width(Length::Fixed(width))
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user