mirror of
https://github.com/danbulant/cushy
synced 2026-06-24 17:12:11 +00:00
ButtonClick + Overlays at locations
The overlay example now supports right-clicking to open overlays at the clicked location, showing how a context menu widget can begin being built.
This commit is contained in:
parent
ecf0d45bfa
commit
1c8d4e0176
20 changed files with 165 additions and 76 deletions
|
|
@ -68,6 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `PlatformWindowImplementation::set_cursor_icon` and
|
- `PlatformWindowImplementation::set_cursor_icon` and
|
||||||
`PlatformWindow::set_cursor_icon` have been renamed to `set_cursor` and accept
|
`PlatformWindow::set_cursor_icon` have been renamed to `set_cursor` and accept
|
||||||
`winit` 0.30's new `Cursor` type.
|
`winit` 0.30's new `Cursor` type.
|
||||||
|
- `Button::on_click` now takes a `Option<ButtonClick>` structure. When this
|
||||||
|
value is provided, information about the mouse click that caused the event is
|
||||||
|
provided.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::time::Duration;
|
||||||
use cushy::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
|
use cushy::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
|
||||||
use cushy::value::{Destination, Dynamic};
|
use cushy::value::{Destination, Dynamic};
|
||||||
use cushy::widget::MakeWidget;
|
use cushy::widget::MakeWidget;
|
||||||
|
use cushy::widgets::button::ButtonClick;
|
||||||
use cushy::widgets::progress::Progressable;
|
use cushy::widgets::progress::Progressable;
|
||||||
use cushy::{Run, WithClone};
|
use cushy::{Run, WithClone};
|
||||||
use figures::units::Lp;
|
use figures::units::Lp;
|
||||||
|
|
@ -41,7 +42,7 @@ fn animate_to(
|
||||||
animation: &Dynamic<AnimationHandle>,
|
animation: &Dynamic<AnimationHandle>,
|
||||||
value: &Dynamic<u8>,
|
value: &Dynamic<u8>,
|
||||||
target: u8,
|
target: u8,
|
||||||
) -> impl FnMut(()) {
|
) -> impl FnMut(Option<ButtonClick>) {
|
||||||
(animation, value).with_clone(|(animation, value)| {
|
(animation, value).with_clone(|(animation, value)| {
|
||||||
move |_| {
|
move |_| {
|
||||||
// Here we use spawn to schedule the animation, which returns an
|
// Here we use spawn to schedule the animation, which returns an
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ fn main() -> cushy::Result {
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click({
|
.on_click({
|
||||||
let task = dynamic.clone();
|
let task = dynamic.clone();
|
||||||
move |()| {
|
move |_| {
|
||||||
let background_task = Task::default();
|
let background_task = Task::default();
|
||||||
spawn_background_thread(&background_task.progress, &task);
|
spawn_background_thread(&background_task.progress, &task);
|
||||||
task.set(Some(background_task));
|
task.set(Some(background_task));
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ fn main() -> cushy::Result {
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click({
|
.on_click({
|
||||||
let clicked_button = clicked_button.clone();
|
let clicked_button = clicked_button.clone();
|
||||||
move |()| clicked_button.set("inner button clicked")
|
move |_| clicked_button.set("inner button clicked")
|
||||||
})
|
})
|
||||||
.with(&ButtonHoverBackground, Color::RED)
|
.with(&ButtonHoverBackground, Color::RED)
|
||||||
.with(&ButtonHoverForeground, Color::WHITE);
|
.with(&ButtonHoverForeground, Color::WHITE);
|
||||||
|
|
@ -40,7 +40,7 @@ fn main() -> cushy::Result {
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click({
|
.on_click({
|
||||||
let clicked_button = clicked_button.clone();
|
let clicked_button = clicked_button.clone();
|
||||||
move |()| clicked_button.set("middle button clicked")
|
move |_| clicked_button.set("middle button clicked")
|
||||||
});
|
});
|
||||||
|
|
||||||
let outer_button = "Yo dawg!"
|
let outer_button = "Yo dawg!"
|
||||||
|
|
@ -49,7 +49,7 @@ fn main() -> cushy::Result {
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click({
|
.on_click({
|
||||||
let clicked_button = clicked_button.clone();
|
let clicked_button = clicked_button.clone();
|
||||||
move |()| clicked_button.set("outer button clicked")
|
move |_| clicked_button.set("outer button clicked")
|
||||||
});
|
});
|
||||||
|
|
||||||
outer_button
|
outer_button
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ fn main() -> cushy::Result {
|
||||||
checkbox_state
|
checkbox_state
|
||||||
.clone()
|
.clone()
|
||||||
.to_checkbox(label)
|
.to_checkbox(label)
|
||||||
.and("Maybe".into_button().on_click(move |()| {
|
.and("Maybe".into_button().on_click(move |_| {
|
||||||
checkbox_state.set(CheckboxState::Indeterminant);
|
checkbox_state.set(CheckboxState::Indeterminant);
|
||||||
}))
|
}))
|
||||||
.into_columns()
|
.into_columns()
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ fn edit_contact_form(contact: &Contact, db: &Dynamic<HashMap<u64, Contact>>) ->
|
||||||
.on_click({
|
.on_click({
|
||||||
let contact_id = contact.id;
|
let contact_id = contact.id;
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
move |()| {
|
move |_| {
|
||||||
let mut db = db.lock();
|
let mut db = db.lock();
|
||||||
let contact = db.get_mut(&contact_id).expect("missing contact");
|
let contact = db.get_mut(&contact_id).expect("missing contact");
|
||||||
contact.first_name = first.get();
|
contact.first_name = first.get();
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ fn main() -> cushy::Result {
|
||||||
let info = info.clone();
|
let info = info.clone();
|
||||||
let window_count = window_count.clone();
|
let window_count = window_count.clone();
|
||||||
let total_windows = total_windows.clone();
|
let total_windows = total_windows.clone();
|
||||||
move |()| open_a_window(&window_count, &total_windows, &info, &mut app)
|
move |_| open_a_window(&window_count, &total_windows, &info, &mut app)
|
||||||
})
|
})
|
||||||
.make_widget();
|
.make_widget();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ fn main() -> cushy::Result {
|
||||||
.and(
|
.and(
|
||||||
"Log In"
|
"Log In"
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click(validations.when_valid(move |()| {
|
.on_click(validations.when_valid(move |_| {
|
||||||
println!("Welcome, {}", username.get());
|
println!("Welcome, {}", username.get());
|
||||||
exit(0);
|
exit(0);
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ fn open_window_button(
|
||||||
let open_windows = open_windows.clone();
|
let open_windows = open_windows.clone();
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
let texture = texture.clone();
|
let texture = texture.clone();
|
||||||
"Open Another Window".into_button().on_click(move |()| {
|
"Open Another Window".into_button().on_click(move |_| {
|
||||||
open_another_window(&mut app, &open_windows, &counter, &texture);
|
open_another_window(&mut app, &open_windows, &counter, &texture);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +82,7 @@ fn open_another_window(
|
||||||
.and(
|
.and(
|
||||||
"Close"
|
"Close"
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click(move |()| handle.request_close()),
|
.on_click(move |_| handle.request_close()),
|
||||||
)
|
)
|
||||||
.into_rows()
|
.into_rows()
|
||||||
.centered(),
|
.centered(),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use cushy::widgets::layers::{OverlayBuilder, OverlayLayer};
|
||||||
use cushy::Run;
|
use cushy::Run;
|
||||||
use figures::units::Lp;
|
use figures::units::Lp;
|
||||||
use figures::{Point, Zero};
|
use figures::{Point, Zero};
|
||||||
|
use kludgine::app::winit::event::MouseButton;
|
||||||
use kludgine::Color;
|
use kludgine::Color;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
|
@ -55,10 +56,20 @@ fn show_overlay_button(
|
||||||
direction_func: impl for<'a> Fn(OverlayBuilder<'a>) -> OverlayBuilder<'a> + Send + 'static,
|
direction_func: impl for<'a> Fn(OverlayBuilder<'a>) -> OverlayBuilder<'a> + Send + 'static,
|
||||||
) -> impl MakeWidget {
|
) -> impl MakeWidget {
|
||||||
let overlay = overlay.clone();
|
let overlay = overlay.clone();
|
||||||
label.into_button().on_click(move |()| {
|
let button_tag = WidgetTag::unique();
|
||||||
direction_func(overlay.build_overlay(test_widget(&overlay, false)))
|
let button_id = button_tag.id();
|
||||||
.hide_on_unhover()
|
label
|
||||||
.show()
|
.into_button()
|
||||||
.forget();
|
.on_click(move |click| {
|
||||||
})
|
let overlay = overlay.build_overlay(test_widget(&overlay, false));
|
||||||
|
let overlay = match click {
|
||||||
|
Some(click) if click.mouse_button == MouseButton::Right => {
|
||||||
|
overlay.above(button_id).at(click.window_location)
|
||||||
|
}
|
||||||
|
_ => direction_func(overlay),
|
||||||
|
};
|
||||||
|
|
||||||
|
overlay.hide_on_unhover().show().forget();
|
||||||
|
})
|
||||||
|
.make_with_tag(button_tag)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ fn main() -> cushy::Result {
|
||||||
.and(editors.neutral_variant.1)
|
.and(editors.neutral_variant.1)
|
||||||
.and("Copy to Clipboard".into_button().on_click({
|
.and("Copy to Clipboard".into_button().on_click({
|
||||||
let cushy = app.cushy().clone();
|
let cushy = app.cushy().clone();
|
||||||
move |()| {
|
move |_| {
|
||||||
if let Some(mut clipboard) = cushy.clipboard_guard() {
|
if let Some(mut clipboard) = cushy.clipboard_guard() {
|
||||||
let builder = color_scheme_builder.get();
|
let builder = color_scheme_builder.get();
|
||||||
let mut source = String::default();
|
let mut source = String::default();
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,13 @@ fn main() -> cushy::Result {
|
||||||
.and(
|
.and(
|
||||||
"Submit"
|
"Submit"
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click(validations.clone().when_valid(move |()| {
|
.on_click(validations.clone().when_valid(move |_| {
|
||||||
println!(
|
println!(
|
||||||
"Success! This callback only happens when all associated validations are valid"
|
"Success! This callback only happens when all associated validations are valid"
|
||||||
);
|
);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.and("Reset".into_button().on_click(move |()| {
|
.and("Reset".into_button().on_click(move |_| {
|
||||||
let _value = text.take();
|
let _value = text.take();
|
||||||
validations.reset();
|
validations.reset();
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
12
src/debug.rs
12
src/debug.rs
|
|
@ -52,7 +52,7 @@ impl DebugContext {
|
||||||
let id = self.section.map_ref(|section| {
|
let id = self.section.map_ref(|section| {
|
||||||
section.values.lock().push(Box::new(RegisteredValue {
|
section.values.lock().push(Box::new(RegisteredValue {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
value: reader.clone(),
|
_value: reader.clone(),
|
||||||
widget: make_observer(value.weak_clone()).make_widget(),
|
widget: make_observer(value.weak_clone()).make_widget(),
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
@ -145,13 +145,13 @@ impl Drop for DebugContext {
|
||||||
|
|
||||||
trait Observable: Send {
|
trait Observable: Send {
|
||||||
fn label(&self) -> &str;
|
fn label(&self) -> &str;
|
||||||
fn alive(&self) -> bool;
|
// fn alive(&self) -> bool;
|
||||||
fn widget(&self) -> &WidgetInstance;
|
fn widget(&self) -> &WidgetInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RegisteredValue<T> {
|
struct RegisteredValue<T> {
|
||||||
label: String,
|
label: String,
|
||||||
value: DynamicReader<T>,
|
_value: DynamicReader<T>,
|
||||||
widget: WidgetInstance,
|
widget: WidgetInstance,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,9 +163,9 @@ where
|
||||||
&self.label
|
&self.label
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alive(&self) -> bool {
|
// fn alive(&self) -> bool {
|
||||||
self.value.connected()
|
// self.value.connected()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn widget(&self) -> &WidgetInstance {
|
fn widget(&self) -> &WidgetInstance {
|
||||||
&self.widget
|
&self.widget
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ pub struct Button {
|
||||||
/// The label to display on the button.
|
/// The label to display on the button.
|
||||||
pub content: WidgetRef,
|
pub content: WidgetRef,
|
||||||
/// The callback that is invoked when the button is clicked.
|
/// The callback that is invoked when the button is clicked.
|
||||||
pub on_click: Option<Callback<()>>,
|
pub on_click: Option<Callback<Option<ButtonClick>>>,
|
||||||
/// The kind of button to draw.
|
/// The kind of button to draw.
|
||||||
pub kind: Value<ButtonKind>,
|
pub kind: Value<ButtonKind>,
|
||||||
focusable: bool,
|
focusable: bool,
|
||||||
|
|
@ -158,7 +158,7 @@ impl Button {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_click<F>(mut self, callback: F) -> Self
|
pub fn on_click<F>(mut self, callback: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnMut(()) + Send + 'static,
|
F: FnMut(Option<ButtonClick>) + Send + 'static,
|
||||||
{
|
{
|
||||||
self.on_click = Some(Callback::new(callback));
|
self.on_click = Some(Callback::new(callback));
|
||||||
self
|
self
|
||||||
|
|
@ -171,10 +171,10 @@ impl Button {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invoke_on_click(&mut self, context: &WidgetContext<'_>) {
|
fn invoke_on_click(&mut self, button: Option<ButtonClick>, context: &WidgetContext<'_>) {
|
||||||
if context.enabled() {
|
if context.enabled() {
|
||||||
if let Some(on_click) = self.on_click.as_mut() {
|
if let Some(on_click) = self.on_click.as_mut() {
|
||||||
on_click.invoke(());
|
on_click.invoke(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +461,7 @@ impl Widget for Button {
|
||||||
&mut self,
|
&mut self,
|
||||||
location: Option<Point<Px>>,
|
location: Option<Point<Px>>,
|
||||||
_device_id: DeviceId,
|
_device_id: DeviceId,
|
||||||
_button: MouseButton,
|
button: MouseButton,
|
||||||
context: &mut EventContext<'_>,
|
context: &mut EventContext<'_>,
|
||||||
) {
|
) {
|
||||||
let window_local = self.per_window.entry(context).or_default();
|
let window_local = self.per_window.entry(context).or_default();
|
||||||
|
|
@ -470,12 +470,19 @@ impl Widget for Button {
|
||||||
context.deactivate();
|
context.deactivate();
|
||||||
|
|
||||||
if let (true, Some(location)) = (self.focusable, location) {
|
if let (true, Some(location)) = (self.focusable, location) {
|
||||||
if Rect::from(context.last_layout().expect("must have been rendered").size)
|
let last_layout = context.last_layout().expect("must have been rendered");
|
||||||
.contains(location)
|
// let button_relative
|
||||||
{
|
if Rect::from(last_layout.size).contains(location) {
|
||||||
context.focus();
|
context.focus();
|
||||||
|
|
||||||
self.invoke_on_click(context);
|
self.invoke_on_click(
|
||||||
|
Some(ButtonClick {
|
||||||
|
mouse_button: button,
|
||||||
|
location,
|
||||||
|
window_location: location + last_layout.origin,
|
||||||
|
}),
|
||||||
|
context,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -533,7 +540,7 @@ impl Widget for Button {
|
||||||
// If we have no buttons pressed, the event should fire on activate not
|
// If we have no buttons pressed, the event should fire on activate not
|
||||||
// on deactivate.
|
// on deactivate.
|
||||||
if window_local.buttons_pressed == 0 {
|
if window_local.buttons_pressed == 0 {
|
||||||
self.invoke_on_click(context);
|
self.invoke_on_click(None, context);
|
||||||
}
|
}
|
||||||
self.update_colors(context, true);
|
self.update_colors(context, true);
|
||||||
}
|
}
|
||||||
|
|
@ -581,3 +588,14 @@ define_components! {
|
||||||
ButtonDisabledOutline(Color, "disabled_outline_color", Color::CLEAR_BLACK)
|
ButtonDisabledOutline(Color, "disabled_outline_color", Color::CLEAR_BLACK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mouse click in a [`Button`].
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct ButtonClick {
|
||||||
|
/// The mouse button that caused the event.
|
||||||
|
pub mouse_button: MouseButton,
|
||||||
|
/// The location relative to the button of the click.
|
||||||
|
pub location: Point<Px>,
|
||||||
|
/// The location relative to the window of the click.
|
||||||
|
pub window_location: Point<Px>,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ impl MakeWidgetWithTag for Checkbox {
|
||||||
.and(self.label)
|
.and(self.label)
|
||||||
.into_columns()
|
.into_columns()
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click(move |()| {
|
.on_click(move |_| {
|
||||||
let mut value = self.state.lock();
|
let mut value = self.state.lock();
|
||||||
*value = !*value;
|
*value = !*value;
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -856,6 +856,12 @@ impl<const N: usize> DerefMut for GridWidgets<N> {
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct GridSection<const N: usize>([WidgetInstance; N]);
|
pub struct GridSection<const N: usize>([WidgetInstance; N]);
|
||||||
|
|
||||||
|
impl Default for GridSection<0> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GridSection<0> {
|
impl GridSection<0> {
|
||||||
/// Returns an empty section.
|
/// Returns an empty section.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
||||||
|
|
@ -1243,9 +1243,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
struct NotVisible(Point<Px>, usize);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct BlinkState {
|
struct BlinkState {
|
||||||
visible: bool,
|
visible: bool,
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ impl OverlayLayer {
|
||||||
layout: OverlayLayout {
|
layout: OverlayLayout {
|
||||||
widget: WidgetRef::new(overlay),
|
widget: WidgetRef::new(overlay),
|
||||||
relative_to: None,
|
relative_to: None,
|
||||||
direction: Direction::Right,
|
positioning: Position::Relative(Direction::Right),
|
||||||
requires_hover: false,
|
requires_hover: false,
|
||||||
on_dismiss: None,
|
on_dismiss: None,
|
||||||
layout: None,
|
layout: None,
|
||||||
|
|
@ -395,26 +395,26 @@ impl OverlayState {
|
||||||
context: &mut LayoutContext<'_, '_, '_, '_>,
|
context: &mut LayoutContext<'_, '_, '_, '_>,
|
||||||
relative_to: WidgetId,
|
relative_to: WidgetId,
|
||||||
) -> Option<Rect<Px>> {
|
) -> Option<Rect<Px>> {
|
||||||
let direction = self.overlays[index].direction;
|
let positioning = self.overlays[index].positioning;
|
||||||
let relative_to = relative_to.find_in(context)?.last_layout()?;
|
let relative_to = relative_to.find_in(context)?.last_layout()?;
|
||||||
let relative_to_unsigned = relative_to.into_unsigned();
|
let relative_to_unsigned = relative_to.into_unsigned();
|
||||||
|
|
||||||
let constraints = match direction {
|
let constraints = match positioning {
|
||||||
Direction::Up => Size::new(
|
Position::Relative(Direction::Up) => Size::new(
|
||||||
relative_to_unsigned.size.width,
|
relative_to_unsigned.size.width,
|
||||||
relative_to_unsigned.origin.y,
|
relative_to_unsigned.origin.y,
|
||||||
),
|
),
|
||||||
Direction::Down => Size::new(
|
Position::Relative(Direction::Down) => Size::new(
|
||||||
relative_to_unsigned.size.width,
|
relative_to_unsigned.size.width,
|
||||||
available_space.height
|
available_space.height
|
||||||
- relative_to_unsigned.origin.y
|
- relative_to_unsigned.origin.y
|
||||||
- relative_to_unsigned.size.height,
|
- relative_to_unsigned.size.height,
|
||||||
),
|
),
|
||||||
Direction::Left => Size::new(
|
Position::Relative(Direction::Left) => Size::new(
|
||||||
relative_to_unsigned.origin.x,
|
relative_to_unsigned.origin.x,
|
||||||
relative_to_unsigned.size.height,
|
relative_to_unsigned.size.height,
|
||||||
),
|
),
|
||||||
Direction::Right => Size::new(
|
Position::Relative(Direction::Right) => Size::new(
|
||||||
available_space.width.saturating_sub(
|
available_space.width.saturating_sub(
|
||||||
relative_to_unsigned
|
relative_to_unsigned
|
||||||
.origin
|
.origin
|
||||||
|
|
@ -423,6 +423,7 @@ impl OverlayState {
|
||||||
),
|
),
|
||||||
relative_to_unsigned.size.height,
|
relative_to_unsigned.size.height,
|
||||||
),
|
),
|
||||||
|
Position::At(_) => available_space,
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = context
|
let size = context
|
||||||
|
|
@ -430,26 +431,39 @@ impl OverlayState {
|
||||||
.layout(constraints.map(ConstraintLimit::SizeToFit))
|
.layout(constraints.map(ConstraintLimit::SizeToFit))
|
||||||
.into_signed();
|
.into_signed();
|
||||||
|
|
||||||
let mut layout_direction = direction;
|
let mut layout_direction = positioning;
|
||||||
let mut layout;
|
let mut layout;
|
||||||
loop {
|
loop {
|
||||||
let origin = match layout_direction {
|
let (origin, intersection_matters) = match layout_direction {
|
||||||
Direction::Up => Point::new(
|
Position::Relative(Direction::Up) => (
|
||||||
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
|
Point::new(
|
||||||
relative_to.origin.y - size.height,
|
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
|
||||||
|
relative_to.origin.y - size.height,
|
||||||
|
),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
Direction::Down => Point::new(
|
Position::Relative(Direction::Down) => (
|
||||||
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
|
Point::new(
|
||||||
relative_to.origin.y + relative_to.size.height,
|
relative_to.origin.x + relative_to.size.width / 2 - size.width / 2,
|
||||||
|
relative_to.origin.y + relative_to.size.height,
|
||||||
|
),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
Direction::Left => Point::new(
|
Position::Relative(Direction::Left) => (
|
||||||
relative_to.origin.x - size.width,
|
Point::new(
|
||||||
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
|
relative_to.origin.x - size.width,
|
||||||
|
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
|
||||||
|
),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
Direction::Right => Point::new(
|
Position::Relative(Direction::Right) => (
|
||||||
relative_to.origin.x + relative_to.size.width,
|
Point::new(
|
||||||
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
|
relative_to.origin.x + relative_to.size.width,
|
||||||
|
relative_to.origin.y + relative_to.size.height / 2 - size.height / 2,
|
||||||
|
),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
|
Position::At(pt) => (pt, false),
|
||||||
};
|
};
|
||||||
|
|
||||||
layout = Rect::new(origin.max(Point::ZERO), size);
|
layout = Rect::new(origin.max(Point::ZERO), size);
|
||||||
|
|
@ -462,16 +476,27 @@ impl OverlayState {
|
||||||
layout.origin.y -= bottom_right.y - available_space.height.into_signed();
|
layout.origin.y -= bottom_right.y - available_space.height.into_signed();
|
||||||
}
|
}
|
||||||
|
|
||||||
if layout.intersects(&relative_to) || self.layout_intersects(index, &layout, context) {
|
if intersection_matters
|
||||||
layout_direction = layout_direction.next_clockwise();
|
&& (layout.intersects(&relative_to)
|
||||||
if layout_direction == direction {
|
|| self.layout_intersects(index, &layout, context))
|
||||||
// No layout worked optimally.
|
{
|
||||||
|
if let Some(next_direction) = layout_direction.next_clockwise() {
|
||||||
|
if layout_direction == positioning {
|
||||||
|
// No layout worked optimally.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
layout_direction = next_direction;
|
||||||
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO check to ensure the widget is fully on-window, otherwise attempt
|
||||||
|
// to shift it to become visible.
|
||||||
|
|
||||||
Some(layout)
|
Some(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -516,7 +541,7 @@ impl OverlayState {
|
||||||
if let Some(relative_to) = self.overlays[index].relative_to {
|
if let Some(relative_to) = self.overlays[index].relative_to {
|
||||||
self.layout_overlay_relative(index, widget, available_space, context, relative_to)
|
self.layout_overlay_relative(index, widget, available_space, context, relative_to)
|
||||||
} else {
|
} else {
|
||||||
let direction = self.overlays[index].direction;
|
let direction = self.overlays[index].positioning;
|
||||||
let size = context
|
let size = context
|
||||||
.for_other(widget)
|
.for_other(widget)
|
||||||
.layout(available_space.map(ConstraintLimit::SizeToFit))
|
.layout(available_space.map(ConstraintLimit::SizeToFit))
|
||||||
|
|
@ -525,22 +550,23 @@ impl OverlayState {
|
||||||
let available_space = available_space.into_signed();
|
let available_space = available_space.into_signed();
|
||||||
|
|
||||||
let origin = match direction {
|
let origin = match direction {
|
||||||
Direction::Up => Point::new(
|
Position::Relative(Direction::Up) => Point::new(
|
||||||
available_space.width / 2,
|
available_space.width / 2,
|
||||||
(available_space.height - size.height) / 2,
|
(available_space.height - size.height) / 2,
|
||||||
),
|
),
|
||||||
Direction::Down => Point::new(
|
Position::Relative(Direction::Down) => Point::new(
|
||||||
available_space.width / 2,
|
available_space.width / 2,
|
||||||
available_space.height / 2 + size.height / 2,
|
available_space.height / 2 + size.height / 2,
|
||||||
),
|
),
|
||||||
Direction::Right => Point::new(
|
Position::Relative(Direction::Right) => Point::new(
|
||||||
available_space.width / 2 + size.width / 2,
|
available_space.width / 2 + size.width / 2,
|
||||||
available_space.height / 2,
|
available_space.height / 2,
|
||||||
),
|
),
|
||||||
Direction::Left => Point::new(
|
Position::Relative(Direction::Left) => Point::new(
|
||||||
(available_space.width - size.width) / 2,
|
(available_space.width - size.width) / 2,
|
||||||
available_space.height / 2,
|
available_space.height / 2,
|
||||||
),
|
),
|
||||||
|
Position::At(pt) => pt,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Rect::new(origin, size))
|
Some(Rect::new(origin, size))
|
||||||
|
|
@ -592,7 +618,14 @@ impl OverlayBuilder<'_> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn near(mut self, id: WidgetId, direction: Direction) -> Self {
|
pub fn near(mut self, id: WidgetId, direction: Direction) -> Self {
|
||||||
self.layout.relative_to = Some(id);
|
self.layout.relative_to = Some(id);
|
||||||
self.layout.direction = direction;
|
self.layout.positioning = Position::Relative(direction);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows this overlay at a specified window `location`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn at(mut self, location: Point<Px>) -> Self {
|
||||||
|
self.layout.positioning = Position::At(location);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,7 +665,7 @@ struct OverlayLayout {
|
||||||
widget: WidgetRef,
|
widget: WidgetRef,
|
||||||
opacity: Dynamic<ZeroToOne>,
|
opacity: Dynamic<ZeroToOne>,
|
||||||
relative_to: Option<WidgetId>,
|
relative_to: Option<WidgetId>,
|
||||||
direction: Direction,
|
positioning: Position<Px>,
|
||||||
requires_hover: bool,
|
requires_hover: bool,
|
||||||
layout: Option<Rect<Px>>,
|
layout: Option<Rect<Px>>,
|
||||||
on_dismiss: Option<Arc<Mutex<Callback>>>,
|
on_dismiss: Option<Arc<Mutex<Callback>>>,
|
||||||
|
|
@ -654,7 +687,7 @@ impl PartialEq for OverlayLayout {
|
||||||
self.widget == other.widget
|
self.widget == other.widget
|
||||||
&& self.opacity == other.opacity
|
&& self.opacity == other.opacity
|
||||||
&& self.relative_to == other.relative_to
|
&& self.relative_to == other.relative_to
|
||||||
&& self.direction == other.direction
|
&& self.positioning == other.positioning
|
||||||
&& self.requires_hover == other.requires_hover
|
&& self.requires_hover == other.requires_hover
|
||||||
&& self.layout == other.layout
|
&& self.layout == other.layout
|
||||||
&& match (&self.on_dismiss, &other.on_dismiss) {
|
&& match (&self.on_dismiss, &other.on_dismiss) {
|
||||||
|
|
@ -665,6 +698,26 @@ impl PartialEq for OverlayLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An overlay position.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum Position<T> {
|
||||||
|
/// Relative to the parent in a given direction.
|
||||||
|
Relative(Direction),
|
||||||
|
/// At a window coordinate.
|
||||||
|
At(Point<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Position<T> {
|
||||||
|
/// Returns the next direction when rotating clockwise.
|
||||||
|
#[must_use]
|
||||||
|
pub fn next_clockwise(&self) -> Option<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Relative(direction) => Some(Self::Relative(direction.next_clockwise())),
|
||||||
|
Self::At(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A relative direction.
|
/// A relative direction.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
|
@ -674,7 +727,7 @@ pub enum Direction {
|
||||||
Right,
|
Right,
|
||||||
/// Positive along the Y axis.
|
/// Positive along the Y axis.
|
||||||
Down,
|
Down,
|
||||||
/// Legative along the X axis.
|
/// Negative along the X axis.
|
||||||
Left,
|
Left,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ where
|
||||||
.and(self.label)
|
.and(self.label)
|
||||||
.into_columns()
|
.into_columns()
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click(move |()| {
|
.on_click(move |_| {
|
||||||
self.state.set(self.value.clone());
|
self.state.set(self.value.clone());
|
||||||
})
|
})
|
||||||
.kind(self.kind)
|
.kind(self.kind)
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ where
|
||||||
});
|
});
|
||||||
self.label
|
self.label
|
||||||
.into_button()
|
.into_button()
|
||||||
.on_click(move |()| {
|
.on_click(move |_| {
|
||||||
self.state.set(self.value.clone());
|
self.state.set(self.value.clone());
|
||||||
})
|
})
|
||||||
.kind(kind)
|
.kind(kind)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue