mirror of
https://github.com/danbulant/cushy
synced 2026-06-24 17:12:11 +00:00
parent
288119a831
commit
0d34924ddf
6 changed files with 625 additions and 7 deletions
65
examples/overlays.rs
Normal file
65
examples/overlays.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use gooey::widget::{MakeWidget, MakeWidgetWithId, WidgetTag};
|
||||
use gooey::widgets::layers::OverlayLayer;
|
||||
use gooey::Run;
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let overlay = OverlayLayer::default();
|
||||
|
||||
test_widget(&overlay)
|
||||
.centered()
|
||||
.and(overlay)
|
||||
.into_layers()
|
||||
.run()
|
||||
}
|
||||
|
||||
fn test_widget(overlay: &OverlayLayer) -> impl MakeWidget {
|
||||
let (my_tag, my_id) = WidgetTag::new();
|
||||
let right = "Right".into_button().on_click({
|
||||
let overlay = overlay.clone();
|
||||
move |()| {
|
||||
overlay
|
||||
.build_overlay(test_widget(&overlay))
|
||||
.right_of(my_id)
|
||||
.show()
|
||||
.forget();
|
||||
}
|
||||
});
|
||||
let left = "Left".into_button().on_click({
|
||||
let overlay = overlay.clone();
|
||||
move |()| {
|
||||
overlay
|
||||
.build_overlay(test_widget(&overlay))
|
||||
.left_of(my_id)
|
||||
.show()
|
||||
.forget();
|
||||
}
|
||||
});
|
||||
let up = "Up".into_button().on_click({
|
||||
let overlay = overlay.clone();
|
||||
move |()| {
|
||||
overlay
|
||||
.build_overlay(test_widget(&overlay))
|
||||
.above(my_id)
|
||||
.show()
|
||||
.forget();
|
||||
}
|
||||
});
|
||||
let down = "Down".into_button().on_click({
|
||||
let overlay = overlay.clone();
|
||||
move |()| {
|
||||
overlay
|
||||
.build_overlay(test_widget(&overlay))
|
||||
.below(my_id)
|
||||
.show()
|
||||
.forget();
|
||||
}
|
||||
});
|
||||
|
||||
up.centered()
|
||||
.and(left.and(right).into_columns())
|
||||
.and(down.centered())
|
||||
.into_rows()
|
||||
.contain()
|
||||
.pad()
|
||||
.make_with_id(my_tag)
|
||||
}
|
||||
|
|
@ -937,6 +937,14 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns true if `possible_parent` is in this widget's parent list.
|
||||
#[must_use]
|
||||
pub fn is_child_of(&self, possible_parent: &WidgetInstance) -> bool {
|
||||
self.current_node
|
||||
.tree
|
||||
.is_child(self.current_node.node_id, possible_parent)
|
||||
}
|
||||
|
||||
/// Returns true if this widget is enabled.
|
||||
#[must_use]
|
||||
pub const fn enabled(&self) -> bool {
|
||||
|
|
|
|||
18
src/tree.rs
18
src/tree.rs
|
|
@ -343,6 +343,24 @@ impl Tree {
|
|||
data.nodes.get(id).expect("missing widget").parent
|
||||
}
|
||||
|
||||
pub(crate) fn is_child(&self, mut id: LotId, possible_parent: &WidgetInstance) -> bool {
|
||||
let data = self.data.lock().ignore_poison();
|
||||
while let Some(node) = data.nodes.get(id) {
|
||||
if &node.widget == possible_parent {
|
||||
return true;
|
||||
}
|
||||
|
||||
match node.parent {
|
||||
Some(parent) => {
|
||||
id = parent;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn attach_styles(&self, id: LotId, styles: Value<Styles>) {
|
||||
let mut data = self.data.lock().ignore_poison();
|
||||
data.attach_styles(id, styles);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use std::sync::atomic::{self, AtomicU64};
|
|||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use alot::LotId;
|
||||
use intentional::Assert;
|
||||
use kludgine::app::winit::event::{
|
||||
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
};
|
||||
|
|
@ -1367,6 +1368,14 @@ impl<T, R> Debug for Callback<T, R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, R> Eq for Callback<T, R> {}
|
||||
|
||||
impl<T, R> PartialEq for Callback<T, R> {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Callback<T, R> {
|
||||
/// Returns a new instance that calls `function` each time the callback is
|
||||
/// invoked.
|
||||
|
|
@ -1396,6 +1405,56 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A function that can be invoked once with a parameter (`T`) and returns `R`.
|
||||
///
|
||||
/// This type is used by widgets to signal an event that can happen only onceq.
|
||||
pub struct OnceCallback<T = (), R = ()>(Box<dyn OnceCallbackFunction<T, R>>);
|
||||
|
||||
impl<T, R> Debug for OnceCallback<T, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("OnceCallback")
|
||||
.field(&(self as *const Self))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Eq for OnceCallback<T, R> {}
|
||||
|
||||
impl<T, R> PartialEq for OnceCallback<T, R> {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> OnceCallback<T, R> {
|
||||
/// Returns a new instance that calls `function` when the callback is
|
||||
/// invoked.
|
||||
pub fn new<F>(function: F) -> Self
|
||||
where
|
||||
F: FnOnce(T) -> R + Send + UnwindSafe + 'static,
|
||||
{
|
||||
Self(Box::new(Some(function)))
|
||||
}
|
||||
|
||||
/// Invokes the wrapped function and returns the produced value.
|
||||
pub fn invoke(mut self, value: T) -> R {
|
||||
self.0.invoke(value)
|
||||
}
|
||||
}
|
||||
|
||||
trait OnceCallbackFunction<T, R>: Send + UnwindSafe {
|
||||
fn invoke(&mut self, value: T) -> R;
|
||||
}
|
||||
|
||||
impl<T, R, F> OnceCallbackFunction<T, R> for Option<F>
|
||||
where
|
||||
F: FnOnce(T) -> R + Send + UnwindSafe,
|
||||
{
|
||||
fn invoke(&mut self, value: T) -> R {
|
||||
(self.take().assert("invoked once"))(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Widget`] that has been attached to a widget hierarchy.
|
||||
#[derive(Clone)]
|
||||
pub struct ManagedWidget {
|
||||
|
|
@ -1763,10 +1822,15 @@ impl WidgetRef {
|
|||
}
|
||||
|
||||
/// Returns this child, mounting it in the process if necessary.
|
||||
pub fn mounted(&mut self, context: &mut EventContext<'_, '_>) -> ManagedWidget {
|
||||
pub fn mount_if_needed(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
if let WidgetRef::Unmounted(instance) = self {
|
||||
*self = WidgetRef::Mounted(context.push_child(instance.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns this child, mounting it in the process if necessary.
|
||||
pub fn mounted(&mut self, context: &mut EventContext<'_, '_>) -> ManagedWidget {
|
||||
self.mount_if_needed(context);
|
||||
|
||||
let Self::Mounted(widget) = self else {
|
||||
unreachable!("just initialized")
|
||||
|
|
@ -1802,6 +1866,18 @@ impl Debug for WidgetRef {
|
|||
}
|
||||
}
|
||||
|
||||
impl Eq for WidgetRef {}
|
||||
|
||||
impl PartialEq for WidgetRef {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if let (WidgetRef::Mounted(this), WidgetRef::Mounted(other)) = (self, other) {
|
||||
this == other
|
||||
} else {
|
||||
self.widget() == other.widget()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The unique id of a [`WidgetInstance`].
|
||||
///
|
||||
/// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ mod expand;
|
|||
pub mod grid;
|
||||
pub mod input;
|
||||
pub mod label;
|
||||
mod layers;
|
||||
pub mod layers;
|
||||
mod mode_switch;
|
||||
pub mod progress;
|
||||
mod radio;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
//! Widgets that stack in the Z-direction.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use alot::{LotId, OrderedLots};
|
||||
use gooey::widget::{RootBehavior, WidgetInstance};
|
||||
use kludgine::figures::units::UPx;
|
||||
use kludgine::figures::{IntoSigned, Rect, Size, Zero};
|
||||
use intentional::Assert;
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size, Zero};
|
||||
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
use crate::widget::{Children, ManagedWidget, Widget};
|
||||
use crate::value::{Dynamic, Generation, IntoValue, Value};
|
||||
use crate::widget::{
|
||||
Children, MakeWidget, ManagedWidget, OnceCallback, Widget, WidgetId, WidgetRef,
|
||||
};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A Z-direction stack of widgets.
|
||||
|
|
@ -111,7 +117,15 @@ impl Widget for Layers {
|
|||
|
||||
// Now we know the size of the widget, we can request the widgets fill
|
||||
// the allocated space.
|
||||
let layout = Rect::from(size).into_signed();
|
||||
let size = Size::new(
|
||||
available_space
|
||||
.width
|
||||
.fit_measured(size.width, context.gfx.scale()),
|
||||
available_space
|
||||
.height
|
||||
.fit_measured(size.height, context.gfx.scale()),
|
||||
);
|
||||
let layout = Rect::from(size.into_signed());
|
||||
for child in &self.mounted {
|
||||
context
|
||||
.for_other(child)
|
||||
|
|
@ -151,3 +165,440 @@ impl Widget for Layers {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that displays other widgets relative to widgets in another layer.
|
||||
///
|
||||
/// This widget is for use inside of a [`Layers`](crate::widgets::Layers)
|
||||
/// widget.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlayLayer {
|
||||
state: Dynamic<OverlayState>,
|
||||
}
|
||||
|
||||
impl OverlayLayer {
|
||||
/// Returns a builder for a new overlay that can be shown on this layer.
|
||||
pub fn build_overlay(&self, overlay: impl MakeWidget) -> OverlayBuilder<'_> {
|
||||
OverlayBuilder {
|
||||
overlay: self,
|
||||
layout: OverlayLayout {
|
||||
widget: WidgetRef::new(overlay),
|
||||
relative_to: None,
|
||||
direction: Direction::Right,
|
||||
requires_hover: false,
|
||||
on_dismiss: None,
|
||||
layout: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for OverlayLayer {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let mut guard = self.state.lock();
|
||||
let state = &mut *guard;
|
||||
|
||||
for child in &state.overlays {
|
||||
let WidgetRef::Mounted(mounted) = &child.widget else {
|
||||
continue;
|
||||
};
|
||||
|
||||
context.for_other(mounted).redraw();
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let mut guard = self.state.lock();
|
||||
let state = &mut *guard;
|
||||
|
||||
let available_space = available_space.map(ConstraintLimit::max);
|
||||
|
||||
state.process_new_overlays(&mut context.as_event_context());
|
||||
|
||||
for index in 0..state.overlays.len() {
|
||||
let widget = state.overlays[index]
|
||||
.widget
|
||||
.mounted(&mut context.as_event_context());
|
||||
let Some(layout) = state.overlays[index]
|
||||
.layout
|
||||
.or_else(|| state.layout_overlay(index, &widget, available_space, context))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let _ignored = context
|
||||
.for_other(&widget)
|
||||
.layout(layout.size.into_unsigned().map(ConstraintLimit::Fill));
|
||||
|
||||
state.overlays[index].layout = Some(layout);
|
||||
context.set_child_layout(&widget, layout);
|
||||
}
|
||||
|
||||
drop(guard);
|
||||
|
||||
// Now that we're done mutating state, we can register for invalidation
|
||||
// tracking.
|
||||
context.invalidate_when_changed(&self.state);
|
||||
|
||||
// The overlay widget should never actualy impact the layout of other
|
||||
// layers, despite what layouts its children are assigned. This may seem
|
||||
// weird, but it would also be weird for a tooltop to expand its window
|
||||
// when shown.
|
||||
Size::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Default)]
|
||||
struct OverlayState {
|
||||
overlays: OrderedLots<OverlayLayout>,
|
||||
new_overlays: usize,
|
||||
}
|
||||
|
||||
impl OverlayState {
|
||||
fn process_new_overlays(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
while self.new_overlays > 0 {
|
||||
let new_index = self.overlays.len() - self.new_overlays;
|
||||
self.new_overlays -= 1;
|
||||
|
||||
// Determine if new_overlay is relative to an existing overlay
|
||||
let new_overlay = self.overlays.get_mut_by_index(new_index).assert_expected();
|
||||
new_overlay.widget.mount_if_needed(context);
|
||||
|
||||
let mut dismiss_from = 0;
|
||||
if let Some(context) = new_overlay
|
||||
.relative_to
|
||||
.and_then(|id| context.for_other(&id))
|
||||
{
|
||||
for existing in (0..new_index).rev() {
|
||||
if context.is_child_of(self.overlays[existing].widget.widget()) {
|
||||
// Relative to this overlay. Dismiss any overlays
|
||||
// between this and the new one.
|
||||
dismiss_from = existing + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dismiss any overlays that are no longer going to be shown.
|
||||
for index in (dismiss_from..new_index).rev() {
|
||||
self.overlays.remove_by_index(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_overlay_relative(
|
||||
&mut self,
|
||||
index: usize,
|
||||
widget: &ManagedWidget,
|
||||
available_space: Size<UPx>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
relative_to: WidgetId,
|
||||
) -> Option<Rect<Px>> {
|
||||
// TODO resolving a widgetid should probably be easier
|
||||
let direction = self.overlays[index].direction;
|
||||
let relative_to = context
|
||||
.widget
|
||||
.for_other(&relative_to)
|
||||
.map(|c| c.widget().clone())?
|
||||
.last_layout()?;
|
||||
let relative_to_unsigned = relative_to.into_unsigned();
|
||||
|
||||
let constraints = match direction {
|
||||
Direction::Up => Size::new(
|
||||
relative_to_unsigned.size.width,
|
||||
relative_to_unsigned.origin.y,
|
||||
),
|
||||
Direction::Down => Size::new(
|
||||
relative_to_unsigned.size.width,
|
||||
available_space.height
|
||||
- relative_to_unsigned.origin.y
|
||||
- relative_to_unsigned.size.height,
|
||||
),
|
||||
Direction::Left => Size::new(
|
||||
relative_to_unsigned.origin.x,
|
||||
relative_to_unsigned.size.height,
|
||||
),
|
||||
Direction::Right => Size::new(
|
||||
available_space.width.saturating_sub(
|
||||
relative_to_unsigned
|
||||
.origin
|
||||
.x
|
||||
.saturating_add(relative_to_unsigned.size.width),
|
||||
),
|
||||
relative_to_unsigned.size.height,
|
||||
),
|
||||
};
|
||||
|
||||
let size = context
|
||||
.for_other(widget)
|
||||
.layout(constraints.map(ConstraintLimit::SizeToFit))
|
||||
.into_signed();
|
||||
|
||||
let mut layout_direction = direction;
|
||||
let mut layout;
|
||||
loop {
|
||||
let origin = match layout_direction {
|
||||
Direction::Up => Point::new(
|
||||
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
|
||||
relative_to.origin.y - size.height,
|
||||
),
|
||||
Direction::Down => Point::new(
|
||||
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
|
||||
relative_to.origin.y + relative_to.size.height,
|
||||
),
|
||||
Direction::Left => Point::new(
|
||||
relative_to.origin.x - size.width,
|
||||
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
|
||||
),
|
||||
Direction::Right => Point::new(
|
||||
relative_to.origin.x + relative_to.size.width,
|
||||
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
|
||||
),
|
||||
};
|
||||
|
||||
layout = Rect::new(origin.max(Point::ZERO), size);
|
||||
|
||||
let bottom_right = layout.extent();
|
||||
if bottom_right.x > available_space.width {
|
||||
layout.origin.x -= bottom_right.x - available_space.width.into_signed();
|
||||
}
|
||||
if bottom_right.y > available_space.height {
|
||||
layout.origin.y -= bottom_right.y - available_space.height.into_signed();
|
||||
}
|
||||
|
||||
if layout.intersects(&relative_to) || self.layout_intersects(index, &layout, context) {
|
||||
layout_direction = layout_direction.next_clockwise();
|
||||
if layout_direction == direction {
|
||||
// No layout worked optimally.
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(layout)
|
||||
}
|
||||
|
||||
fn layout_intersects(
|
||||
&self,
|
||||
checking_index: usize,
|
||||
layout: &Rect<Px>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> bool {
|
||||
for index in (0..self.overlays.len()).filter(|&i| i != checking_index) {
|
||||
if self.overlays[index]
|
||||
.layout
|
||||
.map_or(false, |check| check.intersects(layout))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the the popup won't also obscure the original content.
|
||||
if checking_index != 0 {
|
||||
if let Some(relative_to) = self.overlays[0]
|
||||
.relative_to
|
||||
.and_then(|relative_to| context.widget.for_other(&relative_to))
|
||||
.and_then(|c| c.widget().last_layout())
|
||||
{
|
||||
if relative_to.intersects(layout) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn layout_overlay(
|
||||
&mut self,
|
||||
index: usize,
|
||||
widget: &ManagedWidget,
|
||||
available_space: Size<UPx>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Option<Rect<Px>> {
|
||||
if let Some(relative_to) = self.overlays[index].relative_to {
|
||||
self.layout_overlay_relative(index, widget, available_space, context, relative_to)
|
||||
} else {
|
||||
let direction = self.overlays[index].direction;
|
||||
let size = context
|
||||
.for_other(widget)
|
||||
.layout(available_space.map(ConstraintLimit::SizeToFit))
|
||||
.into_signed();
|
||||
|
||||
let available_space = available_space.into_signed();
|
||||
|
||||
let origin = match direction {
|
||||
Direction::Up => Point::new(
|
||||
available_space.width / 2,
|
||||
(available_space.height - size.height) / 2,
|
||||
),
|
||||
Direction::Down => Point::new(
|
||||
available_space.width / 2,
|
||||
available_space.height / 2 + size.height / 2,
|
||||
),
|
||||
Direction::Right => Point::new(
|
||||
available_space.width / 2 + size.width / 2,
|
||||
available_space.height / 2,
|
||||
),
|
||||
Direction::Left => Point::new(
|
||||
(available_space.width - size.width) / 2,
|
||||
available_space.height / 2,
|
||||
),
|
||||
};
|
||||
|
||||
Some(Rect::new(origin, size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for overlaying a widget on an [`OverlayLayer`].
|
||||
pub struct OverlayBuilder<'a> {
|
||||
overlay: &'a OverlayLayer,
|
||||
layout: OverlayLayout,
|
||||
}
|
||||
|
||||
impl OverlayBuilder<'_> {
|
||||
/// Sets this overlay to hide automatically when it or its relative widget
|
||||
/// are no longer hovered by the mouse cursor.
|
||||
#[must_use]
|
||||
pub fn hide_on_unhover(mut self) -> Self {
|
||||
self.layout.requires_hover = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show this overlay to the left of the specified widget.
|
||||
#[must_use]
|
||||
pub fn left_of(mut self, id: WidgetId) -> Self {
|
||||
self.layout.relative_to = Some(id);
|
||||
self.layout.direction = Direction::Left;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show this overlay to the right of the specified widget.
|
||||
#[must_use]
|
||||
pub fn right_of(mut self, id: WidgetId) -> Self {
|
||||
self.layout.relative_to = Some(id);
|
||||
self.layout.direction = Direction::Right;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show this overlay to show below the specified widget.
|
||||
#[must_use]
|
||||
pub fn below(mut self, id: WidgetId) -> Self {
|
||||
self.layout.relative_to = Some(id);
|
||||
self.layout.direction = Direction::Down;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show this overlay to show above the specified widget.
|
||||
#[must_use]
|
||||
pub fn above(mut self, id: WidgetId) -> Self {
|
||||
self.layout.relative_to = Some(id);
|
||||
self.layout.direction = Direction::Up;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets `callback` to be invoked once this overlay is dismissed.
|
||||
#[must_use]
|
||||
pub fn on_dismiss(mut self, callback: OnceCallback) -> Self {
|
||||
self.layout.on_dismiss = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
/// Shows this overlay, returning a handle that to the displayed overlay.
|
||||
#[must_use]
|
||||
pub fn show(self) -> OverlayHandle {
|
||||
self.overlay.state.map_mut(|state| {
|
||||
state.new_overlays += 1;
|
||||
OverlayHandle {
|
||||
state: self.overlay.state.clone(),
|
||||
id: state.overlays.push(self.layout),
|
||||
dismiss_on_drop: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct OverlayLayout {
|
||||
widget: WidgetRef,
|
||||
relative_to: Option<WidgetId>,
|
||||
direction: Direction,
|
||||
requires_hover: bool,
|
||||
layout: Option<Rect<Px>>,
|
||||
on_dismiss: Option<OnceCallback>,
|
||||
}
|
||||
|
||||
impl Drop for OverlayLayout {
|
||||
fn drop(&mut self) {
|
||||
if let Some(on_dismiss) = self.on_dismiss.take() {
|
||||
on_dismiss.invoke(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A relative direction.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Direction {
|
||||
/// Negative along the Y axis.
|
||||
Up,
|
||||
/// Positive along the X axis.
|
||||
Right,
|
||||
/// Positive along the Y axis.
|
||||
Down,
|
||||
/// Legative along the X axis.
|
||||
Left,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
/// Returns the next direction when rotating clockwise.
|
||||
#[must_use]
|
||||
pub fn next_clockwise(&self) -> Self {
|
||||
match self {
|
||||
Direction::Up => Direction::Right,
|
||||
Direction::Down => Direction::Left,
|
||||
Direction::Right => Direction::Down,
|
||||
Direction::Left => Direction::Up,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to an overlay that was shown in an [`OverlayLayer`].
|
||||
pub struct OverlayHandle {
|
||||
state: Dynamic<OverlayState>,
|
||||
id: LotId,
|
||||
dismiss_on_drop: bool,
|
||||
}
|
||||
|
||||
impl OverlayHandle {
|
||||
/// Dismisses this overlay and any overlays that have been displayed
|
||||
/// relative to it.
|
||||
pub fn dismiss(self) {
|
||||
drop(self);
|
||||
}
|
||||
|
||||
/// Drops this handle without dismissing the overlay.
|
||||
pub fn forget(mut self) {
|
||||
self.dismiss_on_drop = false;
|
||||
drop(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OverlayHandle {
|
||||
fn drop(&mut self) {
|
||||
if self.dismiss_on_drop {
|
||||
let mut state = self.state.lock();
|
||||
let Some(index) = state.overlays.index_of_id(self.id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
while state.overlays.len() > index {
|
||||
let _removed = state.overlays.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue