forked from 1b-agent/opencadstudio-upstream
Ribbon: - A ▾ selector at the right of the tab row picks the tool-panel density — Auto (size to window), Full, Compact, or Collapsed. The choice is persisted. It hides itself when the tool row is already tight, giving the cramped tab row its space back. Start page: - The Recent Documents card is restyled to match the Supporters card (rounded corners, 20px padding, matching title + colours). - Its scrollbar no longer sits on top of the row ✕ remove buttons (a gutter is reserved). - A footer control sets how many recent files to keep: [-] an editable count [+] with the max shown. Typing applies on Enter; an over-max entry snaps to the max. The limit is persisted and enforced on load and on every open. - The "Support on Patreon" heart is now the SVG icon instead of a font glyph. Config: - Add a shared `config::config_dir()` helper; the recent-files and status-bar stores now go through it instead of each re-deriving the path. New minus.svg icon for the stepper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
652 lines
22 KiB
Rust
652 lines
22 KiB
Rust
//! Adaptive ribbon panels.
|
|
//!
|
|
//! Lays the active module's panels on one row. When they don't all fit, the row
|
|
//! degrades **from the right**, one panel at a time: first a panel shrinks to
|
|
//! compact icon columns, then it collapses to a title button. If even the
|
|
//! all-collapsed row overflows, every button drops to its small icon together,
|
|
//! then the buttons are squeezed. The row's height tracks the tallest shown
|
|
//! panel, so it shrinks as the panels do.
|
|
|
|
use std::cell::RefCell;
|
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
use iced::advanced::layout::{self, Layout};
|
|
use iced::advanced::widget::{self, Widget};
|
|
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
|
use iced::{
|
|
Background, Border, Color, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
|
|
Theme, Vector,
|
|
};
|
|
|
|
use crate::app::Message;
|
|
|
|
/// One panel in its five renderings. `tight` is the collapsed button with a
|
|
/// small representative icon instead of the large one — the last step before the
|
|
/// buttons are squeezed together.
|
|
pub struct Panel<'a> {
|
|
pub id: String,
|
|
pub full: Element<'a, Message>,
|
|
pub compact: Element<'a, Message>,
|
|
pub button: Element<'a, Message>,
|
|
pub tight: Element<'a, Message>,
|
|
pub flyout: Element<'a, Message>,
|
|
}
|
|
|
|
// Number of tree slots per panel: [full, compact, button, tight, flyout].
|
|
const SLOTS: usize = 5;
|
|
|
|
// Per-panel degradation level; also the offset of the shown element within the
|
|
// panel's tree slots. Degrade order (from the right): FULL → COMPACT → COLLAPSED
|
|
// (big-icon button) → TIGHT (small-icon button). `flyout` (slot 4) is overlay
|
|
// only, never a level.
|
|
const FULL: u8 = 0;
|
|
const COMPACT: u8 = 1;
|
|
const COLLAPSED: u8 = 2;
|
|
const TIGHT: u8 = 3;
|
|
|
|
/// When even the all-tight row still overflows, the collapsed buttons are pulled
|
|
/// together by up to this many px per gap — reclaiming their edge padding —
|
|
/// before anything is clipped. Mirrors the tab bar squeezing its gaps shut
|
|
/// before wrapping.
|
|
const MAX_PANEL_SQUEEZE: f32 = 8.0;
|
|
|
|
/// How the ribbon tool panels are sized. `Auto` adapts to the window width (the
|
|
/// step-by-step degradation); the others pin every panel to one density so the
|
|
/// user can override the automatic choice. The selection is persisted.
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
|
pub enum CollapseMode {
|
|
/// Size panels to the window: degrade from the right as space runs out.
|
|
#[default]
|
|
Auto,
|
|
/// Always full-size panels (large buttons), even if they overflow.
|
|
Full,
|
|
/// Always compact panels (small icon columns).
|
|
Compact,
|
|
/// Always collapsed to title buttons.
|
|
Collapsed,
|
|
}
|
|
|
|
impl CollapseMode {
|
|
/// Every mode, in dropdown order.
|
|
pub const ALL: &'static [CollapseMode] = &[
|
|
CollapseMode::Auto,
|
|
CollapseMode::Full,
|
|
CollapseMode::Compact,
|
|
CollapseMode::Collapsed,
|
|
];
|
|
|
|
/// Label shown in the dropdown.
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
CollapseMode::Auto => "Auto",
|
|
CollapseMode::Full => "Full",
|
|
CollapseMode::Compact => "Compact",
|
|
CollapseMode::Collapsed => "Collapsed",
|
|
}
|
|
}
|
|
|
|
/// Stable identifier used for persistence.
|
|
fn id(self) -> &'static str {
|
|
match self {
|
|
CollapseMode::Auto => "auto",
|
|
CollapseMode::Full => "full",
|
|
CollapseMode::Compact => "compact",
|
|
CollapseMode::Collapsed => "collapsed",
|
|
}
|
|
}
|
|
|
|
fn from_id(s: &str) -> Option<Self> {
|
|
CollapseMode::ALL.iter().copied().find(|m| m.id() == s)
|
|
}
|
|
|
|
/// The degradation level every panel is pinned to, or `None` for `Auto`.
|
|
fn forced_level(self) -> Option<u8> {
|
|
match self {
|
|
CollapseMode::Auto => None,
|
|
CollapseMode::Full => Some(FULL),
|
|
CollapseMode::Compact => Some(COMPACT),
|
|
CollapseMode::Collapsed => Some(COLLAPSED),
|
|
}
|
|
}
|
|
|
|
/// Load the saved mode, defaulting to `Auto`.
|
|
pub fn load() -> Self {
|
|
config_path()
|
|
.and_then(|p| std::fs::read_to_string(p).ok())
|
|
.and_then(|s| CollapseMode::from_id(s.trim()))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Persist this mode (best-effort; silent on failure).
|
|
pub fn save(self) {
|
|
let Some(path) = config_path() else { return };
|
|
if let Some(dir) = path.parent() {
|
|
let _ = std::fs::create_dir_all(dir);
|
|
}
|
|
let _ = std::fs::write(path, self.id());
|
|
}
|
|
}
|
|
|
|
/// `<config-dir>/OpenCADStudio/ribbon.txt`, matching the other settings stores.
|
|
fn config_path() -> Option<std::path::PathBuf> {
|
|
Some(crate::config::config_dir()?.join("ribbon.txt"))
|
|
}
|
|
|
|
impl std::fmt::Display for CollapseMode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(self.label())
|
|
}
|
|
}
|
|
|
|
pub struct CollapsePanels<'a> {
|
|
panels: Vec<Panel<'a>>,
|
|
/// Title of the panel whose flyout is open (if any).
|
|
open: Option<String>,
|
|
/// Fallback row height, used only when there are no panels to measure.
|
|
row_h: f32,
|
|
/// Colour of the 1px divider drawn between panels.
|
|
divider: Color,
|
|
/// Chosen degradation level per panel; set during layout.
|
|
levels: RefCell<Vec<u8>>,
|
|
/// If set, the measured row height is written here each layout (read when
|
|
/// anchoring dropdowns below the ribbon).
|
|
height_out: Option<Arc<AtomicU32>>,
|
|
/// If set, `true` is written here whenever the row is in the tight state
|
|
/// (some panel dropped to its small icon), so the tab bar can react.
|
|
tight_out: Option<Arc<AtomicBool>>,
|
|
/// User-chosen density. `Auto` runs the width-based degradation; the others
|
|
/// pin every panel to one level regardless of the window width.
|
|
mode: CollapseMode,
|
|
}
|
|
|
|
impl<'a> CollapsePanels<'a> {
|
|
pub fn new(panels: Vec<Panel<'a>>, open: Option<String>, row_h: f32, divider: Color) -> Self {
|
|
let n = panels.len();
|
|
Self {
|
|
panels,
|
|
open,
|
|
row_h,
|
|
divider,
|
|
levels: RefCell::new(vec![FULL; n]),
|
|
height_out: None,
|
|
tight_out: None,
|
|
mode: CollapseMode::Auto,
|
|
}
|
|
}
|
|
|
|
/// Report the measured row height into `out` on every layout.
|
|
pub fn report_height(mut self, out: Arc<AtomicU32>) -> Self {
|
|
self.height_out = Some(out);
|
|
self
|
|
}
|
|
|
|
/// Report whether the row is in the tight state into `out` on every layout.
|
|
pub fn report_tight(mut self, out: Arc<AtomicBool>) -> Self {
|
|
self.tight_out = Some(out);
|
|
self
|
|
}
|
|
|
|
/// Pin the panels to a density (or `Auto` to size by window width).
|
|
pub fn mode(mut self, mode: CollapseMode) -> Self {
|
|
self.mode = mode;
|
|
self
|
|
}
|
|
|
|
fn shown(&self, i: usize, level: u8) -> &Element<'a, Message> {
|
|
match level {
|
|
FULL => &self.panels[i].full,
|
|
COMPACT => &self.panels[i].compact,
|
|
COLLAPSED => &self.panels[i].button,
|
|
_ => &self.panels[i].tight,
|
|
}
|
|
}
|
|
|
|
fn shown_mut(&mut self, i: usize, level: u8) -> &mut Element<'a, Message> {
|
|
match level {
|
|
FULL => &mut self.panels[i].full,
|
|
COMPACT => &mut self.panels[i].compact,
|
|
COLLAPSED => &mut self.panels[i].button,
|
|
_ => &mut self.panels[i].tight,
|
|
}
|
|
}
|
|
|
|
fn levels_snapshot(&self, n: usize) -> Vec<u8> {
|
|
let mut v = self.levels.borrow().clone();
|
|
v.resize(n, FULL);
|
|
v
|
|
}
|
|
}
|
|
|
|
impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|
fn children(&self) -> Vec<widget::Tree> {
|
|
let mut v = Vec::with_capacity(self.panels.len() * SLOTS);
|
|
for p in &self.panels {
|
|
v.push(widget::Tree::new(&p.full));
|
|
v.push(widget::Tree::new(&p.compact));
|
|
v.push(widget::Tree::new(&p.button));
|
|
v.push(widget::Tree::new(&p.tight));
|
|
v.push(widget::Tree::new(&p.flyout));
|
|
}
|
|
v
|
|
}
|
|
|
|
fn diff(&self, tree: &mut widget::Tree) {
|
|
let mut refs: Vec<&dyn Widget<Message, Theme, Renderer>> = Vec::new();
|
|
for p in &self.panels {
|
|
refs.push(p.full.as_widget());
|
|
refs.push(p.compact.as_widget());
|
|
refs.push(p.button.as_widget());
|
|
refs.push(p.tight.as_widget());
|
|
refs.push(p.flyout.as_widget());
|
|
}
|
|
tree.diff_children(&refs);
|
|
}
|
|
|
|
fn size(&self) -> Size<Length> {
|
|
Size::new(Length::Shrink, Length::Shrink)
|
|
}
|
|
|
|
fn layout(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
renderer: &Renderer,
|
|
limits: &layout::Limits,
|
|
) -> layout::Node {
|
|
let max_w = limits.max().width;
|
|
let natural = layout::Limits::new(Size::ZERO, Size::new(f32::INFINITY, f32::INFINITY));
|
|
let n = self.panels.len();
|
|
|
|
// Measure each panel at all four densities. Width drives the degradation
|
|
// decision; height drives the row height.
|
|
let mut full_w = vec![0.0f32; n];
|
|
let mut compact_w = vec![0.0f32; n];
|
|
let mut button_w = vec![0.0f32; n];
|
|
let mut tight_w = vec![0.0f32; n];
|
|
let mut full_h = vec![0.0f32; n];
|
|
let mut compact_h = vec![0.0f32; n];
|
|
let mut button_h = vec![0.0f32; n];
|
|
let mut tight_h = vec![0.0f32; n];
|
|
for i in 0..n {
|
|
let s = self.panels[i].full.as_widget_mut()
|
|
.layout(&mut tree.children[SLOTS * i], renderer, &natural)
|
|
.size();
|
|
full_w[i] = s.width;
|
|
full_h[i] = s.height;
|
|
let s = self.panels[i].compact.as_widget_mut()
|
|
.layout(&mut tree.children[SLOTS * i + 1], renderer, &natural)
|
|
.size();
|
|
compact_w[i] = s.width;
|
|
compact_h[i] = s.height;
|
|
let s = self.panels[i].button.as_widget_mut()
|
|
.layout(&mut tree.children[SLOTS * i + 2], renderer, &natural)
|
|
.size();
|
|
button_w[i] = s.width;
|
|
button_h[i] = s.height;
|
|
let s = self.panels[i].tight.as_widget_mut()
|
|
.layout(&mut tree.children[SLOTS * i + 3], renderer, &natural)
|
|
.size();
|
|
tight_w[i] = s.width;
|
|
tight_h[i] = s.height;
|
|
}
|
|
|
|
let width_of = |lv: u8, i: usize| -> f32 {
|
|
match lv {
|
|
FULL => full_w[i],
|
|
COMPACT => compact_w[i],
|
|
COLLAPSED => button_w[i],
|
|
_ => tight_w[i],
|
|
}
|
|
};
|
|
let height_of = |lv: u8, i: usize| -> f32 {
|
|
match lv {
|
|
FULL => full_h[i],
|
|
COMPACT => compact_h[i],
|
|
COLLAPSED => button_h[i],
|
|
_ => tight_h[i],
|
|
}
|
|
};
|
|
let total = |levels: &[u8]| -> f32 { (0..n).map(|i| width_of(levels[i], i)).sum() };
|
|
|
|
// A forced mode pins every panel to one level. Otherwise (Auto) degrade
|
|
// from the RIGHT, one panel at a time (gradual): first FULL → COMPACT,
|
|
// then COMPACT → COLLAPSED, each phase only while the row still overflows.
|
|
// If even the all-collapsed row overflows, every button drops to its
|
|
// small icon (tight) together, then the buttons are squeezed.
|
|
let levels = if let Some(level) = self.mode.forced_level() {
|
|
vec![level; n]
|
|
} else {
|
|
let mut levels = vec![FULL; n];
|
|
for degraded in [COMPACT, COLLAPSED] {
|
|
for i in (0..n).rev() {
|
|
if total(&levels) <= max_w {
|
|
break;
|
|
}
|
|
levels[i] = degraded;
|
|
}
|
|
}
|
|
if total(&levels) > max_w {
|
|
levels
|
|
.iter_mut()
|
|
.filter(|l| **l == COLLAPSED)
|
|
.for_each(|l| *l = TIGHT);
|
|
}
|
|
levels
|
|
};
|
|
// The row is "tight" once any panel has dropped to its small icon — the
|
|
// last, most cramped state. The tab bar hides its mode selector then.
|
|
if let Some(out) = &self.tight_out {
|
|
out.store(levels.iter().any(|&l| l == TIGHT), Ordering::Relaxed);
|
|
}
|
|
*self.levels.borrow_mut() = levels.clone();
|
|
|
|
// Same idea as the tab bar squeezing its gaps before wrapping: once every
|
|
// panel is at its tightest and the row STILL overflows, pull the buttons
|
|
// together (up to MAX_PANEL_SQUEEZE per gap, reclaiming their edge
|
|
// padding) so more of them stay on-screen before anything is clipped. In
|
|
// Auto this only fires when everything is already tight, so it never
|
|
// overlaps a full/compact panel; a forced density is left to overflow.
|
|
let squeeze = if self.mode == CollapseMode::Auto && n > 1 && total(&levels) > max_w {
|
|
((total(&levels) - max_w) / (n - 1) as f32).min(MAX_PANEL_SQUEEZE)
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
// The row is as tall as the tallest shown panel, so the ribbon height
|
|
// shrinks as its panels degrade to shorter collapsed / tight buttons.
|
|
let row_h = if n == 0 {
|
|
self.row_h
|
|
} else {
|
|
(0..n)
|
|
.map(|i| height_of(levels[i], i))
|
|
.fold(0.0f32, f32::max)
|
|
};
|
|
|
|
// Place the chosen element for each panel left-to-right.
|
|
let mut children: Vec<layout::Node> = Vec::with_capacity(n);
|
|
let mut x = 0.0f32;
|
|
for i in 0..n {
|
|
if i > 0 {
|
|
x -= squeeze;
|
|
}
|
|
let level = levels[i];
|
|
let tree_idx = SLOTS * i + level as usize;
|
|
let node = self.shown_mut(i, level).as_widget_mut().layout(
|
|
&mut tree.children[tree_idx],
|
|
renderer,
|
|
&natural,
|
|
);
|
|
let h = node.size().height;
|
|
let w = node.size().width;
|
|
let y = ((row_h - h) / 2.0).max(0.0);
|
|
children.push(node.move_to(Point::new(x, y)));
|
|
x += w;
|
|
}
|
|
|
|
if let Some(out) = &self.height_out {
|
|
out.store(row_h.to_bits(), Ordering::Relaxed);
|
|
}
|
|
|
|
layout::Node::with_children(Size::new(x, row_h), children)
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
viewport: &Rectangle,
|
|
) {
|
|
let levels = self.levels_snapshot(self.panels.len());
|
|
for (i, child_layout) in layout.children().enumerate() {
|
|
let level = levels[i];
|
|
let tree_idx = SLOTS * i + level as usize;
|
|
self.shown_mut(i, level).as_widget_mut().update(
|
|
&mut tree.children[tree_idx],
|
|
event,
|
|
child_layout,
|
|
cursor,
|
|
renderer,
|
|
clipboard,
|
|
shell,
|
|
viewport,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn mouse_interaction(
|
|
&self,
|
|
tree: &widget::Tree,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
renderer: &Renderer,
|
|
) -> mouse::Interaction {
|
|
let levels = self.levels_snapshot(self.panels.len());
|
|
let mut interaction = mouse::Interaction::default();
|
|
for (i, child_layout) in layout.children().enumerate() {
|
|
let level = levels[i];
|
|
let tree_idx = SLOTS * i + level as usize;
|
|
let it = self.shown(i, level).as_widget().mouse_interaction(
|
|
&tree.children[tree_idx],
|
|
child_layout,
|
|
cursor,
|
|
viewport,
|
|
renderer,
|
|
);
|
|
if it != mouse::Interaction::default() {
|
|
interaction = it;
|
|
}
|
|
}
|
|
interaction
|
|
}
|
|
|
|
fn operate(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
layout: Layout<'_>,
|
|
renderer: &Renderer,
|
|
operation: &mut dyn widget::Operation,
|
|
) {
|
|
let levels = self.levels_snapshot(self.panels.len());
|
|
for (i, child_layout) in layout.children().enumerate() {
|
|
let level = levels[i];
|
|
let tree_idx = SLOTS * i + level as usize;
|
|
self.shown_mut(i, level).as_widget_mut().operate(
|
|
&mut tree.children[tree_idx],
|
|
child_layout,
|
|
renderer,
|
|
operation,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn draw(
|
|
&self,
|
|
tree: &widget::Tree,
|
|
renderer: &mut Renderer,
|
|
theme: &Theme,
|
|
style: &renderer::Style,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
) {
|
|
let levels = self.levels_snapshot(self.panels.len());
|
|
for (i, child_layout) in layout.children().enumerate() {
|
|
let level = levels[i];
|
|
let tree_idx = SLOTS * i + level as usize;
|
|
self.shown(i, level).as_widget().draw(
|
|
&tree.children[tree_idx],
|
|
renderer,
|
|
theme,
|
|
style,
|
|
child_layout,
|
|
cursor,
|
|
viewport,
|
|
);
|
|
}
|
|
|
|
// 1px divider between adjacent panels, except between two button-form
|
|
// panels (collapsed or tight — they read better with no line between).
|
|
let is_btn = |lv: u8| lv == COLLAPSED || lv == TIGHT;
|
|
let bounds: Vec<Rectangle> = layout.children().map(|l| l.bounds()).collect();
|
|
let wb = layout.bounds();
|
|
for i in 0..self.panels.len().saturating_sub(1) {
|
|
if is_btn(levels[i]) && is_btn(levels[i + 1]) {
|
|
continue;
|
|
}
|
|
let x = bounds[i + 1].x;
|
|
renderer.fill_quad(
|
|
renderer::Quad {
|
|
bounds: Rectangle {
|
|
x,
|
|
y: wb.y,
|
|
width: 1.0,
|
|
height: wb.height,
|
|
},
|
|
border: Border::default(),
|
|
shadow: Shadow::default(),
|
|
snap: true,
|
|
},
|
|
Background::Color(self.divider),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn overlay<'b>(
|
|
&'b mut self,
|
|
tree: &'b mut widget::Tree,
|
|
layout: Layout<'b>,
|
|
_renderer: &Renderer,
|
|
_viewport: &Rectangle,
|
|
translation: Vector,
|
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
|
let levels = self.levels_snapshot(self.panels.len());
|
|
let open_id = self.open.clone()?;
|
|
let p = self.panels.iter().position(|pan| pan.id == open_id)?;
|
|
// Only a button-form panel (collapsed or tight) shows a flyout.
|
|
let lvl = levels.get(p).copied().unwrap_or(FULL);
|
|
if lvl != COLLAPSED && lvl != TIGHT {
|
|
return None;
|
|
}
|
|
|
|
let child_layout = layout.children().nth(p)?;
|
|
let b = child_layout.bounds();
|
|
let anchor = Point::new(b.x + translation.x, b.y + b.height + translation.y);
|
|
|
|
Some(overlay::Element::new(Box::new(FlyoutOverlay {
|
|
flyout: &mut self.panels[p].flyout,
|
|
tree: &mut tree.children[SLOTS * p + 4],
|
|
anchor,
|
|
})))
|
|
}
|
|
}
|
|
|
|
impl<'a> From<CollapsePanels<'a>> for Element<'a, Message> {
|
|
fn from(w: CollapsePanels<'a>) -> Self {
|
|
Element::new(w)
|
|
}
|
|
}
|
|
|
|
/// Overlay that renders an open panel's flyout anchored below its button and
|
|
/// closes it when the user presses outside.
|
|
struct FlyoutOverlay<'a, 'b> {
|
|
flyout: &'b mut Element<'a, Message>,
|
|
tree: &'b mut widget::Tree,
|
|
anchor: Point,
|
|
}
|
|
|
|
impl overlay::Overlay<Message, Theme, Renderer> for FlyoutOverlay<'_, '_> {
|
|
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
|
|
let viewport = Rectangle::with_size(bounds);
|
|
let limits = layout::Limits::new(Size::ZERO, viewport.size());
|
|
let node = self
|
|
.flyout
|
|
.as_widget_mut()
|
|
.layout(self.tree, renderer, &limits);
|
|
let size = node.size();
|
|
let mut x = self.anchor.x;
|
|
let mut y = self.anchor.y;
|
|
if x + size.width > viewport.width {
|
|
x = (viewport.width - size.width).max(0.0);
|
|
}
|
|
if y + size.height > viewport.height {
|
|
y = (self.anchor.y - size.height).max(0.0);
|
|
}
|
|
layout::Node::with_children(size, vec![node]).translate(Vector::new(x, y))
|
|
}
|
|
|
|
fn draw(
|
|
&self,
|
|
renderer: &mut Renderer,
|
|
theme: &Theme,
|
|
style: &renderer::Style,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
) {
|
|
let child = layout.children().next().unwrap();
|
|
self.flyout.as_widget().draw(
|
|
self.tree,
|
|
renderer,
|
|
theme,
|
|
style,
|
|
child,
|
|
cursor,
|
|
&child.bounds(),
|
|
);
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
) {
|
|
let child = layout.children().next().unwrap();
|
|
let vp = child.bounds();
|
|
|
|
if let Event::Mouse(mouse::Event::ButtonPressed(_)) = event {
|
|
if !cursor.is_over(vp) {
|
|
shell.publish(Message::CloseRibbonDropdown);
|
|
shell.capture_event();
|
|
return;
|
|
}
|
|
}
|
|
|
|
self.flyout
|
|
.as_widget_mut()
|
|
.update(self.tree, event, child, cursor, renderer, clipboard, shell, &vp);
|
|
}
|
|
|
|
fn operate(
|
|
&mut self,
|
|
layout: Layout<'_>,
|
|
renderer: &Renderer,
|
|
operation: &mut dyn widget::Operation,
|
|
) {
|
|
let child = layout.children().next().unwrap();
|
|
self.flyout
|
|
.as_widget_mut()
|
|
.operate(self.tree, child, renderer, operation);
|
|
}
|
|
|
|
fn mouse_interaction(
|
|
&self,
|
|
layout: Layout<'_>,
|
|
cursor: mouse::Cursor,
|
|
renderer: &Renderer,
|
|
) -> mouse::Interaction {
|
|
let child = layout.children().next().unwrap();
|
|
self.flyout
|
|
.as_widget()
|
|
.mouse_interaction(self.tree, child, cursor, &child.bounds(), renderer)
|
|
}
|
|
}
|