mirror of
https://github.com/danbulant/cushy
synced 2026-06-08 09:01:12 +00:00
parent
5965f19d27
commit
534f676ef0
8 changed files with 105 additions and 30 deletions
|
|
@ -55,12 +55,12 @@ fn main() -> gooey::Result {
|
||||||
.and(
|
.and(
|
||||||
"Log In"
|
"Log In"
|
||||||
.into_button()
|
.into_button()
|
||||||
.enabled(valid)
|
|
||||||
.on_click(move |_| {
|
.on_click(move |_| {
|
||||||
println!("Welcome, {}", username.get());
|
println!("Welcome, {}", username.get());
|
||||||
exit(0);
|
exit(0);
|
||||||
})
|
})
|
||||||
.make_with_id(login_tag)
|
.make_with_id(login_tag)
|
||||||
|
.with_enabled(valid)
|
||||||
.into_default()
|
.into_default()
|
||||||
.with_next_focus(cancel_id),
|
.with_next_focus(cancel_id),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,12 @@ fn main() -> gooey::Result {
|
||||||
.and(
|
.and(
|
||||||
"Log In"
|
"Log In"
|
||||||
.into_button()
|
.into_button()
|
||||||
.enabled(valid)
|
|
||||||
.on_click(move |_| {
|
.on_click(move |_| {
|
||||||
println!("Welcome, {}", username.get());
|
println!("Welcome, {}", username.get());
|
||||||
exit(0);
|
exit(0);
|
||||||
})
|
})
|
||||||
.into_default(),
|
.into_default()
|
||||||
|
.with_enabled(valid),
|
||||||
)
|
)
|
||||||
.into_columns();
|
.into_columns();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,9 +191,9 @@ fn square(row: usize, column: usize, game: &Dynamic<GameState>) -> impl MakeWidg
|
||||||
label
|
label
|
||||||
.clone()
|
.clone()
|
||||||
.into_button()
|
.into_button()
|
||||||
.enabled(enabled)
|
|
||||||
.kind(ButtonKind::Outline)
|
.kind(ButtonKind::Outline)
|
||||||
.on_click(move |_| game.lock().play(row, column))
|
.on_click(move |_| game.lock().play(row, column))
|
||||||
|
.with_enabled(enabled)
|
||||||
.pad()
|
.pad()
|
||||||
.expand()
|
.expand()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,11 +229,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
focus_changes += 1;
|
focus_changes += 1;
|
||||||
|
|
||||||
self.pending_state.focus = focus.and_then(|mut focus| loop {
|
self.pending_state.focus = focus.and_then(|mut focus| loop {
|
||||||
if focus
|
let mut focus_context = self.for_other(&focus);
|
||||||
.lock()
|
let accept_focus = focus_context.enabled()
|
||||||
.as_widget()
|
&& focus.lock().as_widget().accept_focus(&mut focus_context);
|
||||||
.accept_focus(&mut self.for_other(&focus))
|
drop(focus_context);
|
||||||
{
|
|
||||||
|
if accept_focus {
|
||||||
break Some(focus);
|
break Some(focus);
|
||||||
} else if let Some(next_focus) =
|
} else if let Some(next_focus) =
|
||||||
focus.explicit_focus_target(self.pending_state.focus_is_advancing)
|
focus.explicit_focus_target(self.pending_state.focus_is_advancing)
|
||||||
|
|
@ -707,6 +708,7 @@ pub struct WidgetContext<'context, 'window> {
|
||||||
pending_state: PendingState<'context>,
|
pending_state: PendingState<'context>,
|
||||||
theme_mode: ThemeMode,
|
theme_mode: ThemeMode,
|
||||||
effective_styles: Styles,
|
effective_styles: Styles,
|
||||||
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'context, 'window> WidgetContext<'context, 'window> {
|
impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
|
|
@ -717,6 +719,10 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
window: &'context mut RunningWindow<'window>,
|
window: &'context mut RunningWindow<'window>,
|
||||||
theme_mode: ThemeMode,
|
theme_mode: ThemeMode,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let enabled = current_node.enabled(&WindowHandle {
|
||||||
|
kludgine: window.handle(),
|
||||||
|
redraw_status: redraw_status.clone(),
|
||||||
|
});
|
||||||
Self {
|
Self {
|
||||||
pending_state: PendingState::Owned(PendingWidgetState {
|
pending_state: PendingState::Owned(PendingWidgetState {
|
||||||
focus: current_node
|
focus: current_node
|
||||||
|
|
@ -730,6 +736,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
focus_is_advancing: false,
|
focus_is_advancing: false,
|
||||||
}),
|
}),
|
||||||
effective_styles: current_node.effective_styles(),
|
effective_styles: current_node.effective_styles(),
|
||||||
|
enabled,
|
||||||
current_node,
|
current_node,
|
||||||
redraw_status,
|
redraw_status,
|
||||||
theme: Cow::Borrowed(theme),
|
theme: Cow::Borrowed(theme),
|
||||||
|
|
@ -748,6 +755,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
pending_state: self.pending_state.borrowed(),
|
pending_state: self.pending_state.borrowed(),
|
||||||
theme_mode: self.theme_mode,
|
theme_mode: self.theme_mode,
|
||||||
effective_styles: self.effective_styles.clone(),
|
effective_styles: self.effective_styles.clone(),
|
||||||
|
enabled: self.enabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -774,6 +782,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
};
|
};
|
||||||
WidgetContext {
|
WidgetContext {
|
||||||
effective_styles,
|
effective_styles,
|
||||||
|
enabled: current_node.enabled(&self.handle()),
|
||||||
current_node,
|
current_node,
|
||||||
redraw_status: self.redraw_status,
|
redraw_status: self.redraw_status,
|
||||||
window: &mut *self.window,
|
window: &mut *self.window,
|
||||||
|
|
@ -784,6 +793,12 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this widget is enabled.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn enabled(&self) -> bool {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn parent(&self) -> Option<ManagedWidget> {
|
pub(crate) fn parent(&self) -> Option<ManagedWidget> {
|
||||||
self.current_node.parent()
|
self.current_node.parent()
|
||||||
}
|
}
|
||||||
|
|
@ -1005,6 +1020,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct WindowHandle {
|
pub(crate) struct WindowHandle {
|
||||||
kludgine: kludgine::app::WindowHandle<WindowCommand>,
|
kludgine: kludgine::app::WindowHandle<WindowCommand>,
|
||||||
redraw_status: InvalidationStatus,
|
redraw_status: InvalidationStatus,
|
||||||
|
|
|
||||||
23
src/tree.rs
23
src/tree.rs
|
|
@ -6,6 +6,7 @@ use alot::{LotId, Lots};
|
||||||
use kludgine::figures::units::{Px, UPx};
|
use kludgine::figures::units::{Px, UPx};
|
||||||
use kludgine::figures::{Point, Rect, Size};
|
use kludgine::figures::{Point, Rect, Size};
|
||||||
|
|
||||||
|
use crate::context::WindowHandle;
|
||||||
use crate::styles::{Styles, ThemePair, VisualOrder};
|
use crate::styles::{Styles, ThemePair, VisualOrder};
|
||||||
use crate::utils::IgnorePoison;
|
use crate::utils::IgnorePoison;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
@ -44,6 +45,7 @@ impl Tree {
|
||||||
effective_styles,
|
effective_styles,
|
||||||
theme: None,
|
theme: None,
|
||||||
theme_mode: None,
|
theme_mode: None,
|
||||||
|
invalidation: 0,
|
||||||
});
|
});
|
||||||
data.nodes_by_id.insert(id, node_id);
|
data.nodes_by_id.insert(id, node_id);
|
||||||
if widget.is_default() {
|
if widget.is_default() {
|
||||||
|
|
@ -275,6 +277,25 @@ impl Tree {
|
||||||
data.widget_from_node(id, self)
|
data.widget_from_node(id, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_enabled(&self, mut id: LotId, context: &WindowHandle) -> bool {
|
||||||
|
let data = self.data.lock().ignore_poison();
|
||||||
|
loop {
|
||||||
|
let Some(node) = data.nodes.get(id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !node.widget.enabled(context) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(parent) = node.parent else { break };
|
||||||
|
|
||||||
|
id = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn active_widget(&self) -> Option<LotId> {
|
pub(crate) fn active_widget(&self) -> Option<LotId> {
|
||||||
self.data.lock().ignore_poison().active
|
self.data.lock().ignore_poison().active
|
||||||
}
|
}
|
||||||
|
|
@ -481,6 +502,7 @@ impl TreeData {
|
||||||
let mut node = &mut self.nodes[id];
|
let mut node = &mut self.nodes[id];
|
||||||
while node.layout.is_some() {
|
while node.layout.is_some() {
|
||||||
node.layout = None;
|
node.layout = None;
|
||||||
|
node.invalidation += 1;
|
||||||
node.last_layout_query = None;
|
node.last_layout_query = None;
|
||||||
|
|
||||||
let (true, Some(parent)) = (include_hierarchy, node.parent) else {
|
let (true, Some(parent)) = (include_hierarchy, node.parent) else {
|
||||||
|
|
@ -552,6 +574,7 @@ struct Node {
|
||||||
children: Vec<LotId>,
|
children: Vec<LotId>,
|
||||||
parent: Option<LotId>,
|
parent: Option<LotId>,
|
||||||
layout: Option<Rect<Px>>,
|
layout: Option<Rect<Px>>,
|
||||||
|
invalidation: u64,
|
||||||
last_layout_query: Option<CachedLayoutQuery>,
|
last_layout_query: Option<CachedLayoutQuery>,
|
||||||
associated_styles: Option<Value<Styles>>,
|
associated_styles: Option<Value<Styles>>,
|
||||||
effective_styles: Styles,
|
effective_styles: Styles,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ use kludgine::figures::units::{Px, UPx};
|
||||||
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
||||||
use kludgine::Color;
|
use kludgine::Color;
|
||||||
|
|
||||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
use crate::context::{
|
||||||
|
AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext, WindowHandle,
|
||||||
|
};
|
||||||
use crate::styles::{
|
use crate::styles::{
|
||||||
ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, NamedComponent, Styles,
|
ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, NamedComponent, Styles,
|
||||||
ThemePair, VisualOrder,
|
ThemePair, VisualOrder,
|
||||||
|
|
@ -580,6 +582,19 @@ pub trait MakeWidget: Sized {
|
||||||
self.make_widget().with_next_focus(next_focus)
|
self.make_widget().with_next_focus(next_focus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets this widget to be enabled/disabled based on `enabled` and returns
|
||||||
|
/// self.
|
||||||
|
///
|
||||||
|
/// If this widget is disabled, all children widgets will also be disabled.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function can only be called when one instance of the widget exists.
|
||||||
|
/// If any clones exist, a panic will occur.
|
||||||
|
fn with_enabled(self, enabled: impl IntoValue<bool>) -> WidgetInstance {
|
||||||
|
self.make_widget().with_enabled(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets this widget as a "default" widget.
|
/// Sets this widget as a "default" widget.
|
||||||
///
|
///
|
||||||
/// Default widgets are automatically activated when the user signals they
|
/// Default widgets are automatically activated when the user signals they
|
||||||
|
|
@ -846,6 +861,7 @@ struct WidgetInstanceData {
|
||||||
default: bool,
|
default: bool,
|
||||||
cancel: bool,
|
cancel: bool,
|
||||||
next_focus: Value<Option<WidgetId>>,
|
next_focus: Value<Option<WidgetId>>,
|
||||||
|
enabled: Value<bool>,
|
||||||
widget: Box<Mutex<dyn AnyWidget>>,
|
widget: Box<Mutex<dyn AnyWidget>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -863,6 +879,7 @@ impl WidgetInstance {
|
||||||
default: false,
|
default: false,
|
||||||
cancel: false,
|
cancel: false,
|
||||||
widget: Box::new(Mutex::new(widget)),
|
widget: Box::new(Mutex::new(widget)),
|
||||||
|
enabled: Value::Constant(true),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -901,6 +918,23 @@ impl WidgetInstance {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets this widget to be enabled/disabled based on `enabled` and returns
|
||||||
|
/// self.
|
||||||
|
///
|
||||||
|
/// If this widget is disabled, all children widgets will also be disabled.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function can only be called when one instance of the widget exists.
|
||||||
|
/// If any clones exist, a panic will occur.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_enabled(mut self, enabled: impl IntoValue<bool>) -> WidgetInstance {
|
||||||
|
let data = Arc::get_mut(&mut self.data)
|
||||||
|
.expect("with_enabled can only be called on newly created widget instances");
|
||||||
|
data.enabled = enabled.into_value();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets this widget as a "default" widget.
|
/// Sets this widget as a "default" widget.
|
||||||
///
|
///
|
||||||
/// Default widgets are automatically activated when the user signals they
|
/// Default widgets are automatically activated when the user signals they
|
||||||
|
|
@ -982,6 +1016,13 @@ impl WidgetInstance {
|
||||||
pub fn is_escape(&self) -> bool {
|
pub fn is_escape(&self) -> bool {
|
||||||
self.data.cancel
|
self.data.cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enabled(&self, context: &WindowHandle) -> bool {
|
||||||
|
if let Value::Dynamic(dynamic) = &self.data.enabled {
|
||||||
|
dynamic.redraw_when_changed(context.clone());
|
||||||
|
}
|
||||||
|
self.data.enabled.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<WidgetId> for WidgetInstance {
|
impl AsRef<WidgetId> for WidgetInstance {
|
||||||
|
|
@ -1141,6 +1182,10 @@ impl ManagedWidget {
|
||||||
self.tree.active_widget() == Some(self.node_id)
|
self.tree.active_widget() == Some(self.node_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enabled(&self, handle: &WindowHandle) -> bool {
|
||||||
|
self.tree.is_enabled(self.node_id, handle)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this widget is currently the hovered widget.
|
/// Returns true if this widget is currently the hovered widget.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn hovered(&self) -> bool {
|
pub fn hovered(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ pub struct 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<()>>,
|
||||||
/// The enabled state of the button.
|
|
||||||
pub enabled: Value<bool>,
|
|
||||||
/// The kind of button to draw.
|
/// The kind of button to draw.
|
||||||
pub kind: Value<ButtonKind>,
|
pub kind: Value<ButtonKind>,
|
||||||
buttons_pressed: usize,
|
buttons_pressed: usize,
|
||||||
|
|
@ -130,7 +128,6 @@ impl Button {
|
||||||
Self {
|
Self {
|
||||||
content: content.widget_ref(),
|
content: content.widget_ref(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
enabled: Value::Constant(true),
|
|
||||||
cached_state: CacheState {
|
cached_state: CacheState {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
kind: ButtonKind::default(),
|
kind: ButtonKind::default(),
|
||||||
|
|
@ -161,23 +158,16 @@ impl Button {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the value to use for the button's enabled status.
|
fn invoke_on_click(&mut self, context: &WidgetContext<'_, '_>) {
|
||||||
#[must_use]
|
if context.enabled() {
|
||||||
pub fn enabled(mut self, enabled: impl IntoValue<bool>) -> Self {
|
|
||||||
self.enabled = enabled.into_value();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invoke_on_click(&mut self) {
|
|
||||||
if self.enabled.get() {
|
|
||||||
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(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visual_style(&self, context: &WidgetContext<'_, '_>) -> VisualState {
|
fn visual_style(context: &WidgetContext<'_, '_>) -> VisualState {
|
||||||
if !self.enabled.get_tracked(context) {
|
if !context.enabled() {
|
||||||
VisualState::Disabled
|
VisualState::Disabled
|
||||||
} else if context.active() {
|
} else if context.active() {
|
||||||
VisualState::Active
|
VisualState::Active
|
||||||
|
|
@ -220,7 +210,7 @@ impl Button {
|
||||||
|
|
||||||
fn determine_stateful_colors(&mut self, context: &mut WidgetContext<'_, '_>) -> ButtonColors {
|
fn determine_stateful_colors(&mut self, context: &mut WidgetContext<'_, '_>) -> ButtonColors {
|
||||||
let kind = self.kind.get_tracked(context);
|
let kind = self.kind.get_tracked(context);
|
||||||
let visual_state = self.visual_style(context);
|
let visual_state = Self::visual_style(context);
|
||||||
|
|
||||||
self.cached_state = CacheState {
|
self.cached_state = CacheState {
|
||||||
enabled: !matches!(visual_state, VisualState::Disabled),
|
enabled: !matches!(visual_state, VisualState::Disabled),
|
||||||
|
|
@ -341,7 +331,7 @@ impl VisualState {
|
||||||
impl Widget for Button {
|
impl Widget for Button {
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||||
#![allow(clippy::similar_names)]
|
#![allow(clippy::similar_names)]
|
||||||
let enabled = self.enabled.get_tracked(context);
|
let enabled = context.enabled();
|
||||||
|
|
||||||
// TODO This seems ugly. It needs context, so it can't be moved into the
|
// TODO This seems ugly. It needs context, so it can't be moved into the
|
||||||
// dynamic system.
|
// dynamic system.
|
||||||
|
|
@ -403,7 +393,7 @@ impl Widget for Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
|
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
|
||||||
self.enabled.get() && context.get(&AutoFocusableControls).is_all()
|
context.get(&AutoFocusableControls).is_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_down(
|
fn mouse_down(
|
||||||
|
|
@ -455,7 +445,7 @@ impl Widget for Button {
|
||||||
{
|
{
|
||||||
context.focus();
|
context.focus();
|
||||||
|
|
||||||
self.invoke_on_click();
|
self.invoke_on_click(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,7 +493,7 @@ impl Widget for Button {
|
||||||
let changed = context.activate();
|
let changed = context.activate();
|
||||||
if !changed {
|
if !changed {
|
||||||
// The widget was already active. This is now a repeated keypress
|
// The widget was already active. This is now a repeated keypress
|
||||||
self.invoke_on_click();
|
self.invoke_on_click(context);
|
||||||
}
|
}
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
@ -538,7 +528,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 self.buttons_pressed == 0 {
|
if self.buttons_pressed == 0 {
|
||||||
self.invoke_on_click();
|
self.invoke_on_click(context);
|
||||||
}
|
}
|
||||||
self.update_colors(context, true);
|
self.update_colors(context, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1087,6 +1087,7 @@ pub(crate) mod sealed {
|
||||||
pub transparent: bool,
|
pub transparent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum WindowCommand {
|
pub enum WindowCommand {
|
||||||
Redraw,
|
Redraw,
|
||||||
// RequestClose,
|
// RequestClose,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue