forked from 1b-agent/opencadstudio-upstream
feat(ui): right-edge side toolbar; drop the paper-space ribbon tab
Switching to a paper layout used to hijack the ribbon with a contextual "Layout" tab. Replace that with a reusable right-edge vertical toolbar: a single centred column of icon buttons floated over the canvas, built from a flat ToolDef list and dispatching through the existing RibbonToolClick path (ui::side_toolbar). Paper space now shows Viewport / Page Setup / Export PDF there and leaves the ribbon on the user's tab; the contextual layout tab and its three auto-switch hooks are removed. The toolbar is generic infrastructure for future context action sets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
c9071579a2
commit
71837895b0
6 files changed
+145
-17
No files matched your search
+3
-16
@@ -5005,13 +5005,9 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i].scene.deselect_all();
|
self.tabs[i].scene.deselect_all();
|
||||||
self.tabs[i].scene.restore_saved_camera();
|
self.tabs[i].scene.restore_saved_camera();
|
||||||
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
|
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
|
||||||
if going_to_paper {
|
// Paper-space tools live in the right-edge side toolbar now, so
|
||||||
if let Some(idx) = self.ribbon.layout_module_index() {
|
// entering/leaving a layout no longer hijacks the ribbon tab.
|
||||||
self.ribbon.select(idx);
|
let _ = going_to_paper;
|
||||||
}
|
|
||||||
} else if self.ribbon.active_is_layout() {
|
|
||||||
self.ribbon.select(0);
|
|
||||||
}
|
|
||||||
// Refresh VP freeze columns for the new layout.
|
// Refresh VP freeze columns for the new layout.
|
||||||
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
||||||
let vp_info = self.tabs[i].scene.viewport_list();
|
let vp_info = self.tabs[i].scene.viewport_list();
|
||||||
@@ -5054,9 +5050,6 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i].scene.ensure_sheet_viewport(&new_name);
|
self.tabs[i].scene.ensure_sheet_viewport(&new_name);
|
||||||
self.tabs[i].scene.deselect_all();
|
self.tabs[i].scene.deselect_all();
|
||||||
self.tabs[i].scene.fit_all();
|
self.tabs[i].scene.fit_all();
|
||||||
if let Some(idx) = self.ribbon.layout_module_index() {
|
|
||||||
self.ribbon.select(idx);
|
|
||||||
}
|
|
||||||
self.command_line.push_output(&format!(
|
self.command_line.push_output(&format!(
|
||||||
"Layout \"{new_name}\" created — use MVIEW to add a viewport"
|
"Layout \"{new_name}\" created — use MVIEW to add a viewport"
|
||||||
));
|
));
|
||||||
@@ -5075,12 +5068,6 @@ impl OpenCADStudio {
|
|||||||
if self.tabs[i].scene.delete_layout(&name) {
|
if self.tabs[i].scene.delete_layout(&name) {
|
||||||
self.layout_context_menu = None;
|
self.layout_context_menu = None;
|
||||||
self.layout_rename_state = None;
|
self.layout_rename_state = None;
|
||||||
// If we fell back to Model space, update ribbon.
|
|
||||||
if self.tabs[i].scene.current_layout == "Model"
|
|
||||||
&& self.ribbon.active_is_layout()
|
|
||||||
{
|
|
||||||
self.ribbon.select(0);
|
|
||||||
}
|
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Layout \"{name}\" silindi"));
|
.push_output(&format!("Layout \"{name}\" silindi"));
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
|
|||||||
@@ -1119,6 +1119,16 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paper-space context actions: a right-edge vertical toolbar
|
||||||
|
// (viewport / page setup / plot) instead of a contextual ribbon tab.
|
||||||
|
if is_paper && !tab.is_start {
|
||||||
|
if let Some(tb) = crate::ui::side_toolbar::view(
|
||||||
|
&crate::modules::layout::paper_space_tools(),
|
||||||
|
) {
|
||||||
|
viewport_stack = viewport_stack.push(tb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Quick Properties: compact floating property panel on selection,
|
// Quick Properties: compact floating property panel on selection,
|
||||||
// anchored at the canvas top-left so it doesn't track the cursor.
|
// anchored at the canvas top-left so it doesn't track the cursor.
|
||||||
if self.quick_properties && !tab.is_start {
|
if self.quick_properties && !tab.is_start {
|
||||||
|
|||||||
@@ -6,6 +6,26 @@ pub mod vplayer;
|
|||||||
|
|
||||||
use crate::modules::{CadModule, IconKind, ModuleEvent, RibbonGroup, ToolDef};
|
use crate::modules::{CadModule, IconKind, ModuleEvent, RibbonGroup, ToolDef};
|
||||||
|
|
||||||
|
/// Paper-space context tools, as a flat list for the right-edge side toolbar
|
||||||
|
/// (the contextual ribbon tab is no longer shown). Viewport + plot actions.
|
||||||
|
pub fn paper_space_tools() -> Vec<ToolDef> {
|
||||||
|
vec![
|
||||||
|
mview::tool(),
|
||||||
|
ToolDef {
|
||||||
|
id: "PAGESETUP",
|
||||||
|
label: "Page Setup",
|
||||||
|
icon: IconKind::Glyph("📋"),
|
||||||
|
event: ModuleEvent::Command("PAGESETUP".to_string()),
|
||||||
|
},
|
||||||
|
ToolDef {
|
||||||
|
id: "PLOT",
|
||||||
|
label: "Export PDF",
|
||||||
|
icon: IconKind::Glyph("🖨"),
|
||||||
|
event: ModuleEvent::Command("PLOT".to_string()),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LayoutModule;
|
pub struct LayoutModule;
|
||||||
|
|
||||||
impl CadModule for LayoutModule {
|
impl CadModule for LayoutModule {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub mod properties;
|
|||||||
pub mod ribbon;
|
pub mod ribbon;
|
||||||
pub mod scale_popup;
|
pub mod scale_popup;
|
||||||
pub mod selection_filter_popup;
|
pub mod selection_filter_popup;
|
||||||
|
pub mod side_toolbar;
|
||||||
pub mod shortcuts;
|
pub mod shortcuts;
|
||||||
pub mod snap_popup;
|
pub mod snap_popup;
|
||||||
pub mod statusbar;
|
pub mod statusbar;
|
||||||
|
|||||||
@@ -246,7 +246,9 @@ impl Ribbon {
|
|||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
.spacing(6),
|
.spacing(6),
|
||||||
|row_acc, (i, module)| {
|
|row_acc, (i, module)| {
|
||||||
if module.id() == "layout" && !is_paper {
|
// The Layout module no longer has a ribbon tab — its paper-space
|
||||||
|
// tools live in the right-edge side toolbar (see ui::side_toolbar).
|
||||||
|
if module.id() == "layout" {
|
||||||
return row_acc;
|
return row_acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//! Reusable right-edge vertical toolbar.
|
||||||
|
//!
|
||||||
|
//! A single centred column of icon buttons floated over the right edge of the
|
||||||
|
//! canvas — a lightweight, context-specific alternative to a contextual ribbon
|
||||||
|
//! tab. It is built from a flat list of [`ToolDef`]s and dispatches each tool's
|
||||||
|
//! command through the existing [`Message::RibbonToolClick`] path, so any
|
||||||
|
//! module's tools can drive it. First used for paper-space viewport / plot
|
||||||
|
//! actions; reusable for any future context action set.
|
||||||
|
|
||||||
|
use iced::widget::{button, column, container, svg, text, tooltip};
|
||||||
|
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use crate::modules::{IconKind, ToolDef};
|
||||||
|
|
||||||
|
const PANEL_BG: Color = Color { r: 0.13, g: 0.13, b: 0.13, a: 0.96 };
|
||||||
|
const PANEL_BORDER: Color = Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 };
|
||||||
|
const BTN_HOVER: Color = Color { r: 0.24, g: 0.24, b: 0.24, a: 1.0 };
|
||||||
|
const ICON_SIZE: f32 = 22.0;
|
||||||
|
const BTN_SIZE: f32 = 38.0;
|
||||||
|
/// Gap between the toolbar and the right edge of the canvas.
|
||||||
|
const EDGE_MARGIN: f32 = 8.0;
|
||||||
|
|
||||||
|
fn icon_el(icon: IconKind) -> Element<'static, Message> {
|
||||||
|
match icon {
|
||||||
|
IconKind::Glyph(s) => text(s).size(ICON_SIZE * 0.85).color(Color::WHITE).into(),
|
||||||
|
IconKind::Svg(bytes) => svg(svg::Handle::from_memory(bytes))
|
||||||
|
.width(Length::Fixed(ICON_SIZE))
|
||||||
|
.height(Length::Fixed(ICON_SIZE))
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tip_panel(label: &'static str) -> Element<'static, Message> {
|
||||||
|
container(text(label).size(11).color(Color::WHITE))
|
||||||
|
.padding([2, 6])
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(PANEL_BG)),
|
||||||
|
border: Border {
|
||||||
|
color: PANEL_BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 3.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the floating right-edge vertical toolbar from `tools`, vertically
|
||||||
|
/// centred over the canvas. Returns `None` when `tools` is empty so the caller
|
||||||
|
/// can skip pushing an overlay.
|
||||||
|
pub fn view(tools: &[ToolDef]) -> Option<Element<'static, Message>> {
|
||||||
|
if tools.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut col = column![].spacing(4).align_x(iced::Center);
|
||||||
|
for t in tools {
|
||||||
|
let btn = button(icon_el(t.icon))
|
||||||
|
.on_press(Message::RibbonToolClick {
|
||||||
|
tool_id: t.id.to_string(),
|
||||||
|
event: t.event.clone(),
|
||||||
|
})
|
||||||
|
.width(Length::Fixed(BTN_SIZE))
|
||||||
|
.height(Length::Fixed(BTN_SIZE))
|
||||||
|
.style(|_: &Theme, status| button::Style {
|
||||||
|
background: Some(Background::Color(match status {
|
||||||
|
button::Status::Hovered | button::Status::Pressed => BTN_HOVER,
|
||||||
|
_ => Color::TRANSPARENT,
|
||||||
|
})),
|
||||||
|
border: Border {
|
||||||
|
radius: 3.0.into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
text_color: Color::WHITE,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// Label tooltip on the left so it never runs off the right edge.
|
||||||
|
col = col.push(
|
||||||
|
tooltip(btn, tip_panel(t.label), tooltip::Position::Left).gap(6),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let panel = container(col).padding(4).style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(PANEL_BG)),
|
||||||
|
border: Border {
|
||||||
|
color: PANEL_BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 5.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fill the canvas, pin the panel to the right edge and centre it
|
||||||
|
// vertically — no manual size/position math needed.
|
||||||
|
Some(
|
||||||
|
container(iced::widget::opaque(panel))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.align_x(iced::Right)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.padding(iced::Padding {
|
||||||
|
right: EDGE_MARGIN,
|
||||||
|
..iced::Padding::ZERO
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user