parent
b0806679c9
commit
5ad95d1d2e
2 changed files with 212 additions and 6 deletions
|
|
@ -1353,8 +1353,19 @@ impl OpenCADStudio {
|
||||||
.take(3)
|
.take(3)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
// The command line overlays the bottom of the drawing stack.
|
||||||
|
// Keep context menus above its interactive input/history area.
|
||||||
|
let command_line_inset = if self.command_line.history_open {
|
||||||
|
self.command_line.history_height.clamp(
|
||||||
|
crate::ui::command_line::HISTORY_HEIGHT_MIN,
|
||||||
|
crate::ui::command_line::history_max_height(self.win_size.1),
|
||||||
|
) + 72.0
|
||||||
|
} else {
|
||||||
|
34.0
|
||||||
|
};
|
||||||
viewport_stack = viewport_stack.push(viewport_context_menu_overlay(
|
viewport_stack = viewport_stack.push(viewport_context_menu_overlay(
|
||||||
p,
|
p,
|
||||||
|
command_line_inset,
|
||||||
has_cmd,
|
has_cmd,
|
||||||
has_selection,
|
has_selection,
|
||||||
isolation_active,
|
isolation_active,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
use super::super::Message;
|
use super::super::Message;
|
||||||
|
use iced::advanced::layout;
|
||||||
|
use iced::advanced::mouse;
|
||||||
|
use iced::advanced::overlay;
|
||||||
|
use iced::advanced::renderer;
|
||||||
|
use iced::advanced::widget;
|
||||||
|
use iced::advanced::{Layout, Shell, Widget};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, column, container, mouse_area, row, stack, text, text_input,
|
button, column, container, mouse_area, row, scrollable, stack, text, text_input, Space,
|
||||||
Space,
|
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, Color, Element, Event, Fill, Length, Rectangle, Size, Theme, Vector};
|
||||||
use crate::t;
|
use crate::t;
|
||||||
|
|
||||||
pub(super) fn position_canvas_overlay<'a>(
|
pub(super) fn position_canvas_overlay<'a>(
|
||||||
|
|
@ -734,10 +739,193 @@ fn mtext_editor_content<'a>(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Position a cursor-anchored panel inside the drawing's safe rectangle. The
|
||||||
|
/// panel is laid out first, so edge flipping uses its actual translated size
|
||||||
|
/// instead of estimates. `bottom_inset` reserves overlaid controls such as the
|
||||||
|
/// command line.
|
||||||
|
fn position_canvas_overlay_clamped<'a>(
|
||||||
|
anchor: iced::Point,
|
||||||
|
bottom_inset: f32,
|
||||||
|
panel: Element<'a, Message>,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
Element::new(ClampedPin {
|
||||||
|
content: iced::widget::opaque(panel),
|
||||||
|
anchor,
|
||||||
|
bottom_inset: bottom_inset.max(0.0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClampedPin<'a> {
|
||||||
|
content: Element<'a, Message>,
|
||||||
|
anchor: iced::Point,
|
||||||
|
bottom_inset: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget<Message, Theme, iced::Renderer> for ClampedPin<'_> {
|
||||||
|
fn tag(&self) -> widget::tree::Tag {
|
||||||
|
self.content.as_widget().tag()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> widget::tree::State {
|
||||||
|
self.content.as_widget().state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut widget::Tree) {
|
||||||
|
self.content.as_widget_mut().diff(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
Size::new(Length::Fill, Length::Fill)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut widget::Tree,
|
||||||
|
renderer: &iced::Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
const MARGIN: f32 = 4.0;
|
||||||
|
|
||||||
|
let max = limits.max();
|
||||||
|
let safe = Size::new(
|
||||||
|
(max.width - MARGIN * 2.0).max(0.0),
|
||||||
|
(max.height - self.bottom_inset - MARGIN * 2.0).max(0.0),
|
||||||
|
);
|
||||||
|
let mut node = self.content.as_widget_mut().layout(
|
||||||
|
tree,
|
||||||
|
renderer,
|
||||||
|
&layout::Limits::new(Size::ZERO, safe),
|
||||||
|
);
|
||||||
|
let content = node.size();
|
||||||
|
|
||||||
|
let max_x = (max.width - MARGIN - content.width).max(MARGIN);
|
||||||
|
let right = self.anchor.x;
|
||||||
|
let left = self.anchor.x - content.width;
|
||||||
|
let x = if right <= max_x {
|
||||||
|
right.max(MARGIN)
|
||||||
|
} else if left >= MARGIN {
|
||||||
|
left
|
||||||
|
} else {
|
||||||
|
max_x
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_y =
|
||||||
|
(max.height - self.bottom_inset - MARGIN - content.height).max(MARGIN);
|
||||||
|
let below = self.anchor.y;
|
||||||
|
let above = self.anchor.y - content.height;
|
||||||
|
let y = if below <= max_y {
|
||||||
|
below.max(MARGIN)
|
||||||
|
} else if above >= MARGIN {
|
||||||
|
above
|
||||||
|
} else {
|
||||||
|
max_y
|
||||||
|
};
|
||||||
|
|
||||||
|
node = node.move_to(iced::Point::new(x, y));
|
||||||
|
let size = limits.resolve(Length::Fill, Length::Fill, node.size());
|
||||||
|
layout::Node::with_children(size, vec![node])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut widget::Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &iced::Renderer,
|
||||||
|
operation: &mut dyn widget::Operation,
|
||||||
|
) {
|
||||||
|
self.content.as_widget_mut().operate(
|
||||||
|
tree,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
renderer,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut widget::Tree,
|
||||||
|
event: &Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &iced::Renderer,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.content.as_widget_mut().update(
|
||||||
|
tree,
|
||||||
|
event,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor,
|
||||||
|
renderer,
|
||||||
|
shell,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &widget::Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &iced::Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.content.as_widget().mouse_interaction(
|
||||||
|
tree,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &widget::Tree,
|
||||||
|
renderer: &mut iced::Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
if let Some(clipped_viewport) = bounds.intersection(viewport) {
|
||||||
|
self.content.as_widget().draw(
|
||||||
|
tree,
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor,
|
||||||
|
&clipped_viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
tree: &'b mut widget::Tree,
|
||||||
|
layout: Layout<'b>,
|
||||||
|
renderer: &iced::Renderer,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
translation: Vector,
|
||||||
|
) -> Option<overlay::Element<'b, Message, Theme, iced::Renderer>> {
|
||||||
|
self.content.as_widget_mut().overlay(
|
||||||
|
tree,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
renderer,
|
||||||
|
viewport,
|
||||||
|
translation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Viewport right-click context menu ──────────────────────────────────────
|
// ── Viewport right-click context menu ──────────────────────────────────────
|
||||||
|
|
||||||
pub(super) fn viewport_context_menu_overlay(
|
pub(super) fn viewport_context_menu_overlay(
|
||||||
pos: iced::Point,
|
pos: iced::Point,
|
||||||
|
bottom_inset: f32,
|
||||||
has_cmd: bool,
|
has_cmd: bool,
|
||||||
has_selection: bool,
|
has_selection: bool,
|
||||||
isolation_active: bool,
|
isolation_active: bool,
|
||||||
|
|
@ -884,14 +1072,21 @@ pub(super) fn viewport_context_menu_overlay(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu_col = column(items).spacing(0).width(iced::Length::Fixed(180.0));
|
let menu_col = column(items).spacing(0).width(Length::Fixed(180.0));
|
||||||
|
let menu_col = scrollable(menu_col)
|
||||||
|
.height(Length::Shrink)
|
||||||
|
.direction(scrollable::Direction::Vertical(
|
||||||
|
scrollable::Scrollbar::new()
|
||||||
|
.width(8)
|
||||||
|
.scroller_width(6),
|
||||||
|
));
|
||||||
|
|
||||||
let menu = container(menu_col)
|
let menu = container(menu_col)
|
||||||
.style(container::bordered_box)
|
.style(container::bordered_box)
|
||||||
.padding([4, 0])
|
.padding([4, 0])
|
||||||
.width(iced::Length::Fixed(180.0));
|
.width(Length::Fixed(180.0));
|
||||||
|
|
||||||
position_canvas_overlay(pos, menu.into())
|
position_canvas_overlay_clamped(pos, bottom_inset, menu.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One-shot snap override menu (Shift+RMB, #337): a cursor-anchored grid of
|
/// One-shot snap override menu (Shift+RMB, #337): a cursor-anchored grid of
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue