forked from 1b-agent/opencadstudio-upstream
refactor(tabs): use context menu widget
This commit is contained in:
1 parent
ce1091cea5
commit
73545520c8
8 files changed
+194
-403
No files matched your search
@@ -5,76 +5,3 @@ pub mod scale_popup;
|
||||
pub mod selection_filter_popup;
|
||||
pub mod snap_popup;
|
||||
pub mod units_popup;
|
||||
|
||||
use iced::widget::container;
|
||||
use iced::{Element, Fill, Padding, Rectangle};
|
||||
|
||||
use crate::app::Message;
|
||||
|
||||
/// Decide a status-bar popup's horizontal anchor and paddings from its pill's
|
||||
/// screen bounds. Returns `(align_right, horizontal_pad, bottom_pad)`.
|
||||
///
|
||||
/// The popup opens just above the pill. Horizontally it prefers `prefer_right`
|
||||
/// (right edge aligned to the pill's right, growing left) but flips to the other
|
||||
/// side when that direction would cross the window edge.
|
||||
pub fn popup_anchor(
|
||||
pill: Option<Rectangle>,
|
||||
win: (f32, f32),
|
||||
popup_w: f32,
|
||||
prefer_right: bool,
|
||||
) -> (bool, f32, f32) {
|
||||
let Some(b) = pill else {
|
||||
// No recorded position yet — fall back to the bottom-right corner.
|
||||
return (prefer_right, 4.0, 27.0);
|
||||
};
|
||||
let bottom = (win.1 - b.y).max(0.0);
|
||||
let right_pad = (win.0 - (b.x + b.width)).max(4.0);
|
||||
let left_pad = b.x.max(4.0);
|
||||
|
||||
if prefer_right {
|
||||
// Right-aligned popup grows left; flip left-aligned if it would pass the
|
||||
// left window edge.
|
||||
if (b.x + b.width) - popup_w < 2.0 {
|
||||
(false, left_pad, bottom)
|
||||
} else {
|
||||
(true, right_pad, bottom)
|
||||
}
|
||||
} else {
|
||||
// Left-aligned popup grows right; flip right-aligned if it would pass the
|
||||
// right window edge.
|
||||
if b.x + popup_w > win.0 - 2.0 {
|
||||
(true, right_pad, bottom)
|
||||
} else {
|
||||
(false, left_pad, bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a status-bar popup `panel` in a full-window container positioned just
|
||||
/// above its pill, flipping horizontally to stay on screen.
|
||||
pub fn position_statusbar_popup<'a>(
|
||||
panel: Element<'a, Message>,
|
||||
pill: Option<Rectangle>,
|
||||
win: (f32, f32),
|
||||
popup_w: f32,
|
||||
prefer_right: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let (align_right, h_pad, bottom) = popup_anchor(pill, win, popup_w, prefer_right);
|
||||
let pad = Padding {
|
||||
top: 0.0,
|
||||
bottom,
|
||||
left: if align_right { 0.0 } else { h_pad },
|
||||
right: if align_right { h_pad } else { 0.0 },
|
||||
};
|
||||
let c = container(panel)
|
||||
.align_bottom(Fill)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(pad);
|
||||
let c = if align_right {
|
||||
c.align_right(Fill)
|
||||
} else {
|
||||
c.align_left(Fill)
|
||||
};
|
||||
c.into()
|
||||
}
|
||||
+78
-13
@@ -6,9 +6,10 @@ pub mod status_menu;
|
||||
|
||||
use iced::widget::tooltip::Position as TipPos;
|
||||
use iced::widget::{
|
||||
button, container, mouse_area, row, text, text_input, tooltip,
|
||||
button, column, container, mouse_area, row, text, text_input, tooltip,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
use iced_aw::ContextMenu;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Scrollable id of the status-bar layout-tab strip (retained so the existing
|
||||
@@ -766,6 +767,70 @@ fn osnap_btn<'a>(
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn layout_tab_context_menu(name: String) -> Element<'static, Message> {
|
||||
const MENU_BG: Color = Color {
|
||||
r: 0.17,
|
||||
g: 0.17,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const MENU_BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const ITEM_HOVER: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COLOR: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let item = |label: &'static str, msg: Message| {
|
||||
button(text(label).size(12).color(TEXT_COLOR))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ITEM_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COLOR,
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.padding([4, 12])
|
||||
.width(Length::Fill)
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
item("Rename", Message::LayoutRenameStart(name.clone())),
|
||||
item("Delete", Message::LayoutDelete(name)),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(160),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(MENU_BG)),
|
||||
border: Border {
|
||||
color: MENU_BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 0])
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A layout tab button.
|
||||
///
|
||||
/// When `rename_edit` is `Some(value)` the tab shows an inline text input
|
||||
@@ -884,7 +949,8 @@ fn space_tab<'a>(
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
} else {
|
||||
// Normal clickable tab — left click switches, right click opens context menu.
|
||||
// Normal clickable tab — left click switches. Paper-layout tabs are
|
||||
// wrapped in `ContextMenu`, which owns right-click handling.
|
||||
let display = container(text(label.clone()).size(12).color(text_color))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg(is_active, false))),
|
||||
@@ -894,16 +960,10 @@ fn space_tab<'a>(
|
||||
.padding([4, 10]);
|
||||
|
||||
let switch_msg = Message::LayoutSwitch(label.clone());
|
||||
let ctx_msg = Message::LayoutContextMenu(label.clone());
|
||||
|
||||
// Use mouse_area so we can capture right-click for the context menu.
|
||||
// PosReport records the tab's screen bounds so the context menu can
|
||||
// anchor next to the clicked tab instead of the screen's left edge
|
||||
// (#428).
|
||||
let tab = mouse_area(display)
|
||||
.on_press(switch_msg)
|
||||
.on_right_press(ctx_msg);
|
||||
let tab: Element<'a, Message> = if reorderable_layouts.contains(&label) {
|
||||
let has_context_menu = reorderable_layouts.contains(&label);
|
||||
let report_key = format!("SB_LAYOUT_TAB:{label}");
|
||||
let tab = mouse_area(display).on_press(switch_msg);
|
||||
let tab: Element<'a, Message> = if has_context_menu {
|
||||
crate::ui::wrap_bar::ReorderTab::layout(
|
||||
label.clone(),
|
||||
reorderable_layouts,
|
||||
@@ -914,8 +974,13 @@ fn space_tab<'a>(
|
||||
tab.into()
|
||||
};
|
||||
|
||||
let tab = if has_context_menu {
|
||||
ContextMenu::new(tab, move || layout_tab_context_menu(label.clone())).into()
|
||||
} else {
|
||||
tab
|
||||
};
|
||||
crate::ui::wrap_bar::PosReport::owned(
|
||||
format!("SB_LAYOUT_TAB:{label}"),
|
||||
report_key,
|
||||
tab,
|
||||
)
|
||||
.into()
|
||||
|
||||
Reference in new issue
Block a user