mirror of
https://github.com/danbulant/cushy
synced 2026-05-24 12:28:23 +00:00
parent
5965f19d27
commit
534f676ef0
8 changed files with 105 additions and 30 deletions
|
|
@ -55,12 +55,12 @@ fn main() -> gooey::Result {
|
|||
.and(
|
||||
"Log In"
|
||||
.into_button()
|
||||
.enabled(valid)
|
||||
.on_click(move |_| {
|
||||
println!("Welcome, {}", username.get());
|
||||
exit(0);
|
||||
})
|
||||
.make_with_id(login_tag)
|
||||
.with_enabled(valid)
|
||||
.into_default()
|
||||
.with_next_focus(cancel_id),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@ fn main() -> gooey::Result {
|
|||
.and(
|
||||
"Log In"
|
||||
.into_button()
|
||||
.enabled(valid)
|
||||
.on_click(move |_| {
|
||||
println!("Welcome, {}", username.get());
|
||||
exit(0);
|
||||
})
|
||||
.into_default(),
|
||||
.into_default()
|
||||
.with_enabled(valid),
|
||||
)
|
||||
.into_columns();
|
||||
|
||||
|
|
|
|||
|
|
@ -191,9 +191,9 @@ fn square(row: usize, column: usize, game: &Dynamic<GameState>) -> impl MakeWidg
|
|||
label
|
||||
.clone()
|
||||
.into_button()
|
||||
.enabled(enabled)
|
||||
.kind(ButtonKind::Outline)
|
||||
.on_click(move |_| game.lock().play(row, column))
|
||||
.with_enabled(enabled)
|
||||
.pad()
|
||||
.expand()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,11 +229,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
focus_changes += 1;
|
||||
|
||||
self.pending_state.focus = focus.and_then(|mut focus| loop {
|
||||
if focus
|
||||
.lock()
|
||||
.as_widget()
|
||||
.accept_focus(&mut self.for_other(&focus))
|
||||
{
|
||||
let mut focus_context = self.for_other(&focus);
|
||||
let accept_focus = focus_context.enabled()
|
||||
&& focus.lock().as_widget().accept_focus(&mut focus_context);
|
||||
drop(focus_context);
|
||||
|
||||
if accept_focus {
|
||||
break Some(focus);
|
||||
} else if let Some(next_focus) =
|
||||
focus.explicit_focus_target(self.pending_state.focus_is_advancing)
|
||||
|
|
@ -707,6 +708,7 @@ pub struct WidgetContext<'context, 'window> {
|
|||
pending_state: PendingState<'context>,
|
||||
theme_mode: ThemeMode,
|
||||
effective_styles: Styles,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl<'context, 'window> WidgetContext<'context, 'window> {
|
||||
|
|
@ -717,6 +719,10 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
window: &'context mut RunningWindow<'window>,
|
||||
theme_mode: ThemeMode,
|
||||
) -> Self {
|
||||
let enabled = current_node.enabled(&WindowHandle {
|
||||
kludgine: window.handle(),
|
||||
redraw_status: redraw_status.clone(),
|
||||
});
|
||||
Self {
|
||||
pending_state: PendingState::Owned(PendingWidgetState {
|
||||
focus: current_node
|
||||
|
|
@ -730,6 +736,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
focus_is_advancing: false,
|
||||
}),
|
||||
effective_styles: current_node.effective_styles(),
|
||||
enabled,
|
||||
current_node,
|
||||
redraw_status,
|
||||
theme: Cow::Borrowed(theme),
|
||||
|
|
@ -748,6 +755,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
pending_state: self.pending_state.borrowed(),
|
||||
theme_mode: self.theme_mode,
|
||||
effective_styles: self.effective_styles.clone(),
|
||||
enabled: self.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -774,6 +782,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
};
|
||||
WidgetContext {
|
||||
effective_styles,
|
||||
enabled: current_node.enabled(&self.handle()),
|
||||
current_node,
|
||||
redraw_status: self.redraw_status,
|
||||
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> {
|
||||
self.current_node.parent()
|
||||
}
|
||||
|
|
@ -1005,6 +1020,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WindowHandle {
|
||||
kludgine: kludgine::app::WindowHandle<WindowCommand>,
|
||||
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::{Point, Rect, Size};
|
||||
|
||||
use crate::context::WindowHandle;
|
||||
use crate::styles::{Styles, ThemePair, VisualOrder};
|
||||
use crate::utils::IgnorePoison;
|
||||
use crate::value::Value;
|
||||
|
|
@ -44,6 +45,7 @@ impl Tree {
|
|||
effective_styles,
|
||||
theme: None,
|
||||
theme_mode: None,
|
||||
invalidation: 0,
|
||||
});
|
||||
data.nodes_by_id.insert(id, node_id);
|
||||
if widget.is_default() {
|
||||
|
|
@ -275,6 +277,25 @@ impl Tree {
|
|||
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> {
|
||||
self.data.lock().ignore_poison().active
|
||||
}
|
||||
|
|
@ -481,6 +502,7 @@ impl TreeData {
|
|||
let mut node = &mut self.nodes[id];
|
||||
while node.layout.is_some() {
|
||||
node.layout = None;
|
||||
node.invalidation += 1;
|
||||
node.last_layout_query = None;
|
||||
|
||||
let (true, Some(parent)) = (include_hierarchy, node.parent) else {
|
||||
|
|
@ -552,6 +574,7 @@ struct Node {
|
|||
children: Vec<LotId>,
|
||||
parent: Option<LotId>,
|
||||
layout: Option<Rect<Px>>,
|
||||
invalidation: u64,
|
||||
last_layout_query: Option<CachedLayoutQuery>,
|
||||
associated_styles: Option<Value<Styles>>,
|
||||
effective_styles: Styles,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ use kludgine::figures::units::{Px, UPx};
|
|||
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
|
||||
use crate::context::{
|
||||
AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext, WindowHandle,
|
||||
};
|
||||
use crate::styles::{
|
||||
ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, NamedComponent, Styles,
|
||||
ThemePair, VisualOrder,
|
||||
|
|
@ -580,6 +582,19 @@ pub trait MakeWidget: Sized {
|
|||
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.
|
||||
///
|
||||
/// Default widgets are automatically activated when the user signals they
|
||||
|
|
@ -846,6 +861,7 @@ struct WidgetInstanceData {
|
|||
default: bool,
|
||||
cancel: bool,
|
||||
next_focus: Value<Option<WidgetId>>,
|
||||
enabled: Value<bool>,
|
||||
widget: Box<Mutex<dyn AnyWidget>>,
|
||||
}
|
||||
|
||||
|
|
@ -863,6 +879,7 @@ impl WidgetInstance {
|
|||
default: false,
|
||||
cancel: false,
|
||||
widget: Box::new(Mutex::new(widget)),
|
||||
enabled: Value::Constant(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
@ -901,6 +918,23 @@ impl WidgetInstance {
|
|||
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.
|
||||
///
|
||||
/// Default widgets are automatically activated when the user signals they
|
||||
|
|
@ -982,6 +1016,13 @@ impl WidgetInstance {
|
|||
pub fn is_escape(&self) -> bool {
|
||||
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 {
|
||||
|
|
@ -1141,6 +1182,10 @@ impl ManagedWidget {
|
|||
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.
|
||||
#[must_use]
|
||||
pub fn hovered(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ pub struct Button {
|
|||
pub content: WidgetRef,
|
||||
/// The callback that is invoked when the button is clicked.
|
||||
pub on_click: Option<Callback<()>>,
|
||||
/// The enabled state of the button.
|
||||
pub enabled: Value<bool>,
|
||||
/// The kind of button to draw.
|
||||
pub kind: Value<ButtonKind>,
|
||||
buttons_pressed: usize,
|
||||
|
|
@ -130,7 +128,6 @@ impl Button {
|
|||
Self {
|
||||
content: content.widget_ref(),
|
||||
on_click: None,
|
||||
enabled: Value::Constant(true),
|
||||
cached_state: CacheState {
|
||||
enabled: true,
|
||||
kind: ButtonKind::default(),
|
||||
|
|
@ -161,23 +158,16 @@ impl Button {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the value to use for the button's enabled status.
|
||||
#[must_use]
|
||||
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() {
|
||||
fn invoke_on_click(&mut self, context: &WidgetContext<'_, '_>) {
|
||||
if context.enabled() {
|
||||
if let Some(on_click) = self.on_click.as_mut() {
|
||||
on_click.invoke(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visual_style(&self, context: &WidgetContext<'_, '_>) -> VisualState {
|
||||
if !self.enabled.get_tracked(context) {
|
||||
fn visual_style(context: &WidgetContext<'_, '_>) -> VisualState {
|
||||
if !context.enabled() {
|
||||
VisualState::Disabled
|
||||
} else if context.active() {
|
||||
VisualState::Active
|
||||
|
|
@ -220,7 +210,7 @@ impl Button {
|
|||
|
||||
fn determine_stateful_colors(&mut self, context: &mut WidgetContext<'_, '_>) -> ButtonColors {
|
||||
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 {
|
||||
enabled: !matches!(visual_state, VisualState::Disabled),
|
||||
|
|
@ -341,7 +331,7 @@ impl VisualState {
|
|||
impl Widget for Button {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
#![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
|
||||
// dynamic system.
|
||||
|
|
@ -403,7 +393,7 @@ impl Widget for Button {
|
|||
}
|
||||
|
||||
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(
|
||||
|
|
@ -455,7 +445,7 @@ impl Widget for Button {
|
|||
{
|
||||
context.focus();
|
||||
|
||||
self.invoke_on_click();
|
||||
self.invoke_on_click(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -503,7 +493,7 @@ impl Widget for Button {
|
|||
let changed = context.activate();
|
||||
if !changed {
|
||||
// The widget was already active. This is now a repeated keypress
|
||||
self.invoke_on_click();
|
||||
self.invoke_on_click(context);
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
|
@ -538,7 +528,7 @@ impl Widget for Button {
|
|||
// If we have no buttons pressed, the event should fire on activate not
|
||||
// on deactivate.
|
||||
if self.buttons_pressed == 0 {
|
||||
self.invoke_on_click();
|
||||
self.invoke_on_click(context);
|
||||
}
|
||||
self.update_colors(context, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1087,6 +1087,7 @@ pub(crate) mod sealed {
|
|||
pub transparent: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum WindowCommand {
|
||||
Redraw,
|
||||
// RequestClose,
|
||||
|
|
|
|||
Loading…
Reference in a new issue