mirror of
https://github.com/danbulant/cushy
synced 2026-06-17 05:21:17 +00:00
Button animations, hover fixes, ComponentType
This commit is contained in:
parent
6b8e5f886b
commit
64f46a46e2
15 changed files with 300 additions and 83 deletions
|
|
@ -7,7 +7,7 @@ use gooey::{widgets, Run};
|
|||
fn main() -> gooey::Result {
|
||||
let counter = Dynamic::new(0i32);
|
||||
let label = counter.map_each(ToString::to_string);
|
||||
Scroll::new(Stack::rows(widgets![
|
||||
Scroll::new(Stack::columns(widgets![
|
||||
Label::new(label),
|
||||
Button::new("+").on_click(counter.with_clone(|counter| {
|
||||
move |_| {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use alot::{LotId, Lots};
|
||||
use kempt::Set;
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::value::Dynamic;
|
||||
|
||||
|
|
@ -337,6 +338,25 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoAnimate for Duration {
|
||||
type Animate = Self;
|
||||
|
||||
fn into_animate(self) -> Self::Animate {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Animate for Duration {
|
||||
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
|
||||
if let Some(remaining) = self.checked_sub(elapsed) {
|
||||
*self = remaining;
|
||||
ControlFlow::Continue(())
|
||||
} else {
|
||||
ControlFlow::Break(elapsed - *self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a linear interpolation between two values.
|
||||
pub trait LinearInterpolate {
|
||||
/// Interpolate linearly between `self` and `target` using `percent`.
|
||||
|
|
@ -409,6 +429,17 @@ fn integer_lerps() {
|
|||
test_lerps(&isize::MIN, &isize::MAX, &0);
|
||||
}
|
||||
|
||||
impl LinearInterpolate for Color {
|
||||
fn lerp(&self, target: &Self, percent: f32) -> Self {
|
||||
Color::new(
|
||||
self.red().lerp(&target.red(), percent),
|
||||
self.green().lerp(&target.green(), percent),
|
||||
self.blue().lerp(&target.blue(), percent),
|
||||
self.alpha().lerp(&target.alpha(), percent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An `f32` that is clamped between 0.0 and 1.0 and cannot be NaN or Infinity.
|
||||
///
|
||||
/// Because of these restrictions, this type implements `Ord` and `Eq`.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use kludgine::Kludgine;
|
|||
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::components::HighlightColor;
|
||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
||||
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
|
||||
use crate::value::Dynamic;
|
||||
use crate::widget::{EventHandling, ManagedWidget, WidgetInstance};
|
||||
use crate::window::RunningWindow;
|
||||
|
|
@ -126,31 +126,29 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
}
|
||||
|
||||
pub(crate) fn hover(&mut self, location: Point<Px>) {
|
||||
if let Ok(changes) = self.current_node.tree.hover(Some(self.current_node)) {
|
||||
for unhovered in changes.unhovered {
|
||||
let mut context = self.for_other(&unhovered);
|
||||
unhovered.lock().unhover(&mut context);
|
||||
}
|
||||
for hover in changes.hovered {
|
||||
let mut context = self.for_other(&hover);
|
||||
hover.lock().hover(location, &mut context);
|
||||
}
|
||||
let changes = self.current_node.tree.hover(Some(self.current_node));
|
||||
for unhovered in changes.unhovered {
|
||||
let mut context = self.for_other(&unhovered);
|
||||
unhovered.lock().unhover(&mut context);
|
||||
}
|
||||
for hover in changes.hovered {
|
||||
let mut context = self.for_other(&hover);
|
||||
hover.lock().hover(location, &mut context);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear_hover(&mut self) {
|
||||
if let Ok(changes) = self.current_node.tree.hover(None) {
|
||||
assert!(changes.hovered.is_empty());
|
||||
let changes = self.current_node.tree.hover(None);
|
||||
assert!(changes.hovered.is_empty());
|
||||
|
||||
for old_hover in changes.unhovered {
|
||||
let mut old_hover_context = self.for_other(&old_hover);
|
||||
old_hover.lock().unhover(&mut old_hover_context);
|
||||
}
|
||||
for old_hover in changes.unhovered {
|
||||
let mut old_hover_context = self.for_other(&old_hover);
|
||||
old_hover.lock().unhover(&mut old_hover_context);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_pending_state(&mut self) {
|
||||
let active = self.pending_state.active.take();
|
||||
let active = self.pending_state.active.clone();
|
||||
if self.current_node.tree.active_widget() != active.as_ref().map(|active| active.id) {
|
||||
let new = match self.current_node.tree.activate(active.as_ref()) {
|
||||
Ok(old) => {
|
||||
|
|
@ -164,12 +162,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
};
|
||||
if new {
|
||||
if let Some(active) = active {
|
||||
active.lock().activate(self);
|
||||
active.lock().activate(&mut self.for_other(&active));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let focus = self.pending_state.focus.take();
|
||||
let focus = self.pending_state.focus.clone();
|
||||
if self.current_node.tree.focused_widget() != focus.as_ref().map(|focus| focus.id) {
|
||||
let new = match self.current_node.tree.focus(focus.as_ref()) {
|
||||
Ok(old) => {
|
||||
|
|
@ -183,7 +181,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
};
|
||||
if new {
|
||||
if let Some(focus) = focus {
|
||||
focus.lock().focus(self);
|
||||
focus.lock().focus(&mut self.for_other(&focus));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -296,7 +294,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
|
|||
|
||||
/// Renders the default focus ring for this widget.
|
||||
pub fn draw_focus_ring(&mut self) {
|
||||
self.draw_focus_ring_using(&self.query_style(&[&HighlightColor]));
|
||||
self.draw_focus_ring_using(&self.query_styles(&[&HighlightColor]));
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::measure()`](crate::widget::Widget::measure) on this
|
||||
|
|
@ -559,7 +557,23 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
/// widget is provided as a convenient way to attach styles into the widget
|
||||
/// hierarchy.
|
||||
#[must_use]
|
||||
pub fn query_style(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
||||
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
||||
self.current_node
|
||||
.tree
|
||||
.query_styles(self.current_node, query)
|
||||
}
|
||||
|
||||
/// Queries the widget hierarchy for a single style component.
|
||||
///
|
||||
/// This function traverses up the widget hierarchy looking for the
|
||||
/// component being requested. If a matching component is found, it will be
|
||||
/// returned. Otherwise, the default value will be returned.
|
||||
|
||||
#[must_use]
|
||||
pub fn query_style<Component: ComponentDefinition>(
|
||||
&self,
|
||||
query: &Component,
|
||||
) -> Component::ComponentType {
|
||||
self.current_node.tree.query_style(self.current_node, query)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
/// used.
|
||||
///
|
||||
/// `origin` allows controlling how the text will be drawn relative to the
|
||||
/// coordinate provided in [`render()`](crate::PreparedGraphic::render).
|
||||
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
|
||||
pub fn draw_text_buffer<Unit>(
|
||||
&mut self,
|
||||
buffer: &cosmic_text::Buffer,
|
||||
|
|
@ -244,7 +244,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
/// used.
|
||||
///
|
||||
/// `origin` allows controlling how the text will be drawn relative to the
|
||||
/// coordinate provided in [`render()`](crate::PreparedGraphic::render).
|
||||
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
|
||||
pub fn draw_measured_text<Unit>(
|
||||
&mut self,
|
||||
text: &MeasuredText<Unit>,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@ impl Styles {
|
|||
self.0
|
||||
.get(&name.group)
|
||||
.and_then(|group| group.get(&name.name))
|
||||
.and_then(|component| component.clone().try_into().ok())
|
||||
.and_then(|component| {
|
||||
<Named::ComponentType>::try_from_component(component.clone()).ok()
|
||||
})
|
||||
.unwrap_or_else(|| component.default_value())
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +145,7 @@ pub enum Component {
|
|||
/// A percentage between 0.0 and 1.0.
|
||||
Percent(f32),
|
||||
/// A custom component type.
|
||||
Boxed(CustomComponent),
|
||||
Custom(CustomComponent),
|
||||
}
|
||||
|
||||
impl From<Color> for Component {
|
||||
|
|
@ -223,6 +225,12 @@ pub enum Dimension {
|
|||
Lp(Lp),
|
||||
}
|
||||
|
||||
impl Default for Dimension {
|
||||
fn default() -> Self {
|
||||
Self::Px(Px(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Px> for Dimension {
|
||||
fn from(value: Px) -> Self {
|
||||
Self::Px(value)
|
||||
|
|
@ -286,6 +294,19 @@ impl CustomComponent {
|
|||
}
|
||||
}
|
||||
|
||||
impl ComponentType for CustomComponent {
|
||||
fn into_component(self) -> Component {
|
||||
Component::Custom(self)
|
||||
}
|
||||
|
||||
fn try_from_component(component: Component) -> Result<Self, Component> {
|
||||
match component {
|
||||
Component::Custom(custom) => Ok(custom),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait AnyComponent: Send + Sync + RefUnwindSafe + UnwindSafe + Debug {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
|
@ -378,12 +399,34 @@ pub trait NamedComponent {
|
|||
/// Rust type.
|
||||
pub trait ComponentDefinition: NamedComponent {
|
||||
/// The type that will be contained in the [`Component`].
|
||||
type ComponentType: Into<Component> + TryFrom<Component, Error = Component>;
|
||||
type ComponentType: ComponentType;
|
||||
|
||||
/// Returns the default value to use for this component.
|
||||
fn default_value(&self) -> Self::ComponentType;
|
||||
}
|
||||
|
||||
/// A type that can be converted to and from [`Component`].
|
||||
pub trait ComponentType: Sized {
|
||||
/// Returns this type, wrapped in a [`Component`].
|
||||
fn into_component(self) -> Component;
|
||||
/// Attempts to extract this type from `component`. If `component` does not
|
||||
/// contain this type, `Err(component)` is returned.
|
||||
fn try_from_component(component: Component) -> Result<Self, Component>;
|
||||
}
|
||||
|
||||
impl<T> ComponentType for T
|
||||
where
|
||||
T: Into<Component> + TryFrom<Component, Error = Component>,
|
||||
{
|
||||
fn into_component(self) -> Component {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn try_from_component(component: Component) -> Result<Self, Component> {
|
||||
Self::try_from(component)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that represents a named component with a default value.
|
||||
pub trait ComponentDefaultvalue: NamedComponent {
|
||||
/// Returns the default value for this component.
|
||||
|
|
@ -395,7 +438,7 @@ where
|
|||
T: ComponentDefinition,
|
||||
{
|
||||
fn default_component_value(&self) -> Component {
|
||||
self.default_value().into()
|
||||
self.default_value().into_component()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,3 +77,25 @@ impl ComponentDefinition for HighlightColor {
|
|||
Color::AQUA
|
||||
}
|
||||
}
|
||||
|
||||
/// Intrinsic, uniform padding for a widget.
|
||||
///
|
||||
/// This component is opt-in and does not automatically work for all widgets. To
|
||||
/// apply arbitrary, non-uniform padding around another widget, use a
|
||||
/// [`Cell`](crate::widgets::Cell).
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct IntrinsicPadding;
|
||||
|
||||
impl NamedComponent for IntrinsicPadding {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("padding"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for IntrinsicPadding {
|
||||
type ComponentType = Dimension;
|
||||
|
||||
fn default_value(&self) -> Dimension {
|
||||
Dimension::Lp(Lp::points(5))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
70
src/tree.rs
70
src/tree.rs
|
|
@ -6,7 +6,7 @@ use alot::{LotId, Lots};
|
|||
use kludgine::figures::units::Px;
|
||||
use kludgine::figures::{Point, Rect};
|
||||
|
||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
||||
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
|
||||
use crate::widget::{ManagedWidget, WidgetInstance};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
|
@ -61,31 +61,26 @@ impl Tree {
|
|||
data.render_order.clear();
|
||||
}
|
||||
|
||||
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> Result<HoverResults, ()> {
|
||||
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
let mut hovered = new_hover
|
||||
let hovered = new_hover
|
||||
.map(|new_hover| data.widget_hierarchy(new_hover.id, self))
|
||||
.unwrap_or_default();
|
||||
match data.update_tracked_widget(new_hover, self, |data| &mut data.hover)? {
|
||||
Some(old_hover) => {
|
||||
let unhovered = match data.update_tracked_widget(new_hover, self, |data| &mut data.hover) {
|
||||
Ok(Some(old_hover)) => {
|
||||
let mut old_hovered = data.widget_hierarchy(old_hover.id, self);
|
||||
// For any widgets that were shared, remove them, as they don't
|
||||
// need to have their events fired again.
|
||||
while !old_hovered.is_empty() && old_hovered.get(0) == hovered.get(0) {
|
||||
let mut new_index = 0;
|
||||
while !old_hovered.is_empty() && old_hovered.get(0) == hovered.get(new_index) {
|
||||
old_hovered.remove(0);
|
||||
hovered.remove(0);
|
||||
new_index += 1;
|
||||
}
|
||||
|
||||
Ok(HoverResults {
|
||||
unhovered: old_hovered,
|
||||
hovered,
|
||||
})
|
||||
old_hovered
|
||||
}
|
||||
None => Ok(HoverResults {
|
||||
unhovered: Vec::new(),
|
||||
hovered,
|
||||
}),
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
HoverResults { unhovered, hovered }
|
||||
}
|
||||
|
||||
pub fn focus(&self, new_focus: Option<&ManagedWidget>) -> Result<Option<ManagedWidget>, ()> {
|
||||
|
|
@ -167,7 +162,7 @@ impl Tree {
|
|||
data.nodes[id.0].styles = Some(styles);
|
||||
}
|
||||
|
||||
pub fn query_style(
|
||||
pub fn query_styles(
|
||||
&self,
|
||||
perspective: &ManagedWidget,
|
||||
query: &[&dyn ComponentDefaultvalue],
|
||||
|
|
@ -175,7 +170,18 @@ impl Tree {
|
|||
self.data
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
.query_style(perspective.id, query)
|
||||
.query_styles(perspective.id, query)
|
||||
}
|
||||
|
||||
pub fn query_style<Component: ComponentDefinition>(
|
||||
&self,
|
||||
perspective: &ManagedWidget,
|
||||
component: &Component,
|
||||
) -> Component::ComponentType {
|
||||
self.data
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
.query_style(perspective.id, component)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +261,7 @@ impl TreeData {
|
|||
}
|
||||
}
|
||||
|
||||
fn query_style(
|
||||
fn query_styles(
|
||||
&self,
|
||||
mut perspective: WidgetId,
|
||||
query: &[&dyn ComponentDefaultvalue],
|
||||
|
|
@ -279,6 +285,30 @@ impl TreeData {
|
|||
}
|
||||
resolved
|
||||
}
|
||||
|
||||
fn query_style<Component: ComponentDefinition>(
|
||||
&self,
|
||||
mut perspective: WidgetId,
|
||||
query: &Component,
|
||||
) -> Component::ComponentType {
|
||||
let name = query.name();
|
||||
loop {
|
||||
let node = &self.nodes[perspective.0];
|
||||
if let Some(styles) = &node.styles {
|
||||
if let Some(component) = styles.get(&name) {
|
||||
let Ok(value) =
|
||||
<Component::ComponentType>::try_from_component(component.clone())
|
||||
else {
|
||||
break;
|
||||
};
|
||||
return value;
|
||||
}
|
||||
}
|
||||
let Some(parent) = node.parent else { break };
|
||||
perspective = parent;
|
||||
}
|
||||
query.default_value()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
|
|
|
|||
|
|
@ -433,6 +433,15 @@ impl<T> Value<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Value<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::Constant(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can be converted into a [`Value`].
|
||||
pub trait IntoValue<T> {
|
||||
/// Returns this type as a [`Value`].
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//! Built-in [`Widget`](crate::widget::Widget) implementations.
|
||||
|
||||
mod button;
|
||||
pub mod button;
|
||||
mod canvas;
|
||||
mod input;
|
||||
mod label;
|
||||
mod scroll;
|
||||
pub mod scroll;
|
||||
pub mod stack;
|
||||
mod style;
|
||||
mod tilemap;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
//! A clickable, labeled button
|
||||
use std::borrow::Cow;
|
||||
use std::panic::UnwindSafe;
|
||||
use std::time::Duration;
|
||||
|
||||
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
||||
use kludgine::app::winit::keyboard::KeyCode;
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{IntoUnsigned, Point, Rect, Size};
|
||||
use kludgine::figures::{IntoUnsigned, Point, Rect, ScreenScale, Size};
|
||||
use kludgine::shapes::Shape;
|
||||
use kludgine::text::Text;
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::context::{EventContext, GraphicsContext};
|
||||
use crate::animation::{Animation, AnimationHandle, Spawn};
|
||||
use crate::context::{EventContext, GraphicsContext, WidgetContext};
|
||||
use crate::names::Name;
|
||||
use crate::styles::components::{HighlightColor, TextColor};
|
||||
use crate::styles::components::{HighlightColor, IntrinsicPadding, TextColor};
|
||||
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
|
||||
use crate::value::{IntoValue, Value};
|
||||
use crate::value::{Dynamic, IntoValue, Value};
|
||||
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
|
||||
|
||||
/// A clickable button.
|
||||
|
|
@ -24,6 +27,8 @@ pub struct Button {
|
|||
/// The callback that is invoked when the button is clicked.
|
||||
pub on_click: Option<Callback<()>>,
|
||||
buttons_pressed: usize,
|
||||
background_color: Option<Dynamic<Color>>,
|
||||
background_color_animation: AnimationHandle,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
|
|
@ -33,6 +38,8 @@ impl Button {
|
|||
label: label.into_value(),
|
||||
on_click: None,
|
||||
buttons_pressed: 0,
|
||||
background_color: None,
|
||||
background_color_animation: AnimationHandle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +60,50 @@ impl Button {
|
|||
on_click.invoke(());
|
||||
}
|
||||
}
|
||||
|
||||
fn update_background_color(&mut self, context: &WidgetContext<'_, '_>, immediate: bool) {
|
||||
let styles = context.query_styles(&[
|
||||
&ButtonActiveBackground,
|
||||
&ButtonBackground,
|
||||
&ButtonHoverBackground,
|
||||
]);
|
||||
let background_color = if context.active() {
|
||||
styles.get_or_default(&ButtonActiveBackground)
|
||||
} else if context.hovered() {
|
||||
styles.get_or_default(&ButtonHoverBackground)
|
||||
} else {
|
||||
styles.get_or_default(&ButtonBackground)
|
||||
};
|
||||
|
||||
match (immediate, &self.background_color) {
|
||||
(false, Some(dynamic)) => {
|
||||
self.background_color_animation = Animation::linear(
|
||||
dynamic.clone(),
|
||||
background_color,
|
||||
Duration::from_millis(150),
|
||||
)
|
||||
.spawn();
|
||||
}
|
||||
(true, Some(dynamic)) => {
|
||||
dynamic.set(background_color);
|
||||
self.background_color_animation.clear();
|
||||
}
|
||||
(_, None) => {
|
||||
let dynamic = Dynamic::new(background_color);
|
||||
self.background_color = Some(dynamic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_background_color(&mut self, context: &WidgetContext<'_, '_>) -> Color {
|
||||
if self.background_color.is_none() {
|
||||
self.update_background_color(context, false);
|
||||
}
|
||||
|
||||
let background_color = self.background_color.as_ref().expect("always initialized");
|
||||
context.redraw_when_changed(background_color);
|
||||
background_color.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Button {
|
||||
|
|
@ -61,7 +112,7 @@ impl Widget for Button {
|
|||
let center = Point::from(size) / 2;
|
||||
self.label.redraw_when_changed(context);
|
||||
|
||||
let styles = context.query_style(&[
|
||||
let styles = context.query_styles(&[
|
||||
&TextColor,
|
||||
&HighlightColor,
|
||||
&ButtonActiveBackground,
|
||||
|
|
@ -71,13 +122,7 @@ impl Widget for Button {
|
|||
|
||||
let visible_rect = Rect::from(size - (Px(1), Px(1)));
|
||||
|
||||
let background = if context.active() {
|
||||
styles.get_or_default(&ButtonActiveBackground)
|
||||
} else if context.hovered() {
|
||||
styles.get_or_default(&ButtonHoverBackground)
|
||||
} else {
|
||||
styles.get_or_default(&ButtonBackground)
|
||||
};
|
||||
let background = self.current_background_color(context);
|
||||
let background = Shape::filled_rect(visible_rect, background);
|
||||
context
|
||||
.graphics
|
||||
|
|
@ -173,6 +218,10 @@ impl Widget for Button {
|
|||
available_space: Size<crate::ConstraintLimit>,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let padding = context
|
||||
.query_style(&IntrinsicPadding)
|
||||
.into_px(context.graphics.scale())
|
||||
.into_unsigned();
|
||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||
self.label.map(|label| {
|
||||
let measured = context
|
||||
|
|
@ -180,7 +229,8 @@ impl Widget for Button {
|
|||
.measure_text::<Px>(Text::from(label).wrap_at(width));
|
||||
|
||||
let mut size = measured.size.into_unsigned();
|
||||
size.height = size.height.max(measured.line_height.into_unsigned());
|
||||
size.width += padding * 2;
|
||||
size.height = size.height.max(measured.line_height.into_unsigned()) + padding * 2;
|
||||
size
|
||||
})
|
||||
}
|
||||
|
|
@ -210,11 +260,11 @@ impl Widget for Button {
|
|||
}
|
||||
|
||||
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
context.set_needs_redraw();
|
||||
self.update_background_color(context, false);
|
||||
}
|
||||
|
||||
fn hover(&mut self, _location: Point<Px>, context: &mut EventContext<'_, '_>) {
|
||||
context.set_needs_redraw();
|
||||
self.update_background_color(context, false);
|
||||
}
|
||||
|
||||
fn focus(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
|
|
@ -226,11 +276,11 @@ impl Widget for Button {
|
|||
}
|
||||
|
||||
fn activate(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
context.set_needs_redraw();
|
||||
self.update_background_color(context, true);
|
||||
}
|
||||
|
||||
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
context.set_needs_redraw();
|
||||
self.update_background_color(context, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,6 +290,7 @@ impl ComponentGroup for Button {
|
|||
}
|
||||
}
|
||||
|
||||
/// The background color of the button.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct ButtonBackground;
|
||||
|
||||
|
|
@ -257,6 +308,7 @@ impl ComponentDefinition for ButtonBackground {
|
|||
}
|
||||
}
|
||||
|
||||
/// The background color of the button when it is active (depressed).
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct ButtonActiveBackground;
|
||||
|
||||
|
|
@ -274,6 +326,8 @@ impl ComponentDefinition for ButtonActiveBackground {
|
|||
}
|
||||
}
|
||||
|
||||
/// The background color of the button when the mouse cursor is hovering over
|
||||
/// it.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct ButtonHoverBackground;
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl Input {
|
|||
}
|
||||
|
||||
fn styles(context: &WidgetContext<'_, '_>) -> Styles {
|
||||
context.query_style(&[&TextColor, &TextSize, &LineHeight])
|
||||
context.query_styles(&[&TextColor, &TextSize, &LineHeight])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ impl Widget for Input {
|
|||
context: &mut EventContext<'_, '_>,
|
||||
) -> EventHandling {
|
||||
context.focus();
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let styles = context.query_styles(&[&TextColor]);
|
||||
self.editor_mut(context.kludgine, &styles).action(
|
||||
context.kludgine.font_system(),
|
||||
Action::Click {
|
||||
|
|
@ -124,7 +124,7 @@ impl Widget for Input {
|
|||
_button: kludgine::app::winit::event::MouseButton,
|
||||
context: &mut EventContext<'_, '_>,
|
||||
) {
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let styles = context.query_styles(&[&TextColor]);
|
||||
self.editor_mut(context.kludgine, &styles).action(
|
||||
context.kludgine.font_system(),
|
||||
Action::Drag {
|
||||
|
|
@ -141,7 +141,7 @@ impl Widget for Input {
|
|||
self.cursor_state.update(context.elapsed());
|
||||
let cursor_state = self.cursor_state;
|
||||
let size = context.graphics.size();
|
||||
let styles = context.query_style(&[&TextColor, &HighlightColor]);
|
||||
let styles = context.query_styles(&[&TextColor, &HighlightColor]);
|
||||
let highlight = styles.get_or_default(&HighlightColor);
|
||||
let editor = self.editor_mut(&mut context.graphics, &styles);
|
||||
let cursor = editor.cursor();
|
||||
|
|
@ -293,7 +293,7 @@ impl Widget for Input {
|
|||
available_space: kludgine::figures::Size<crate::ConstraintLimit>,
|
||||
context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>,
|
||||
) -> kludgine::figures::Size<kludgine::figures::units::UPx> {
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let styles = context.query_styles(&[&TextColor]);
|
||||
let editor = self.editor_mut(&mut context.graphics, &styles);
|
||||
let buffer = editor.buffer_mut();
|
||||
buffer.set_size(
|
||||
|
|
@ -319,7 +319,7 @@ impl Widget for Input {
|
|||
return IGNORED;
|
||||
}
|
||||
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let styles = context.query_styles(&[&TextColor]);
|
||||
let editor = self.editor_mut(context.kludgine, &styles);
|
||||
|
||||
println!(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{Point, Size};
|
||||
use kludgine::figures::{IntoUnsigned, Point, ScreenScale, Size};
|
||||
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||
|
||||
use crate::context::GraphicsContext;
|
||||
use crate::styles::components::TextColor;
|
||||
use crate::styles::components::{IntrinsicPadding, TextColor};
|
||||
use crate::value::{IntoValue, Value};
|
||||
use crate::widget::Widget;
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl Widget for Label {
|
|||
|
||||
let size = context.graphics.region().size;
|
||||
let center = Point::from(size) / 2;
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let styles = context.query_styles(&[&TextColor]);
|
||||
|
||||
if let Some(measured) = &self.prepared_text {
|
||||
context
|
||||
|
|
@ -56,12 +56,17 @@ impl Widget for Label {
|
|||
available_space: Size<crate::ConstraintLimit>,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let padding = context
|
||||
.query_style(&IntrinsicPadding)
|
||||
.into_px(context.graphics.scale())
|
||||
.into_unsigned();
|
||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||
self.text.map(|contents| {
|
||||
let measured = context
|
||||
.graphics
|
||||
.measure_text(Text::from(contents).wrap_at(width));
|
||||
let size = measured.size.try_cast().unwrap_or_default();
|
||||
let mut size = measured.size.try_cast().unwrap_or_default();
|
||||
size += padding * 2;
|
||||
self.prepared_text = Some(measured);
|
||||
size
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
//! A container that scrolls its contents on a virtual surface.
|
||||
use std::borrow::Cow;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -10,7 +11,7 @@ use kludgine::figures::{
|
|||
use kludgine::shapes::Shape;
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::animation::{Animation, AnimationHandle, Spawn, ZeroToOne};
|
||||
use crate::animation::{Animation, AnimationHandle, IntoAnimate, Spawn, ZeroToOne};
|
||||
use crate::context::{AsEventContext, EventContext};
|
||||
use crate::styles::{
|
||||
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
|
||||
|
|
@ -82,6 +83,12 @@ impl Widget for Scroll {
|
|||
ZeroToOne::ONE,
|
||||
Duration::from_millis(300),
|
||||
)
|
||||
.chain(Duration::from_secs(1))
|
||||
.chain(Animation::linear(
|
||||
self.scrollbar_opacity.clone(),
|
||||
ZeroToOne::ZERO,
|
||||
Duration::from_millis(300),
|
||||
))
|
||||
.spawn();
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +108,7 @@ impl Widget for Scroll {
|
|||
return;
|
||||
};
|
||||
let visible_bottom_right = visible_rect.into_signed().extent();
|
||||
let styles = context.query_style(&[&ScrollBarThickness]);
|
||||
let styles = context.query_styles(&[&ScrollBarThickness]);
|
||||
let bar_width = styles
|
||||
.get_or_default(&ScrollBarThickness)
|
||||
.into_px(context.graphics.scale());
|
||||
|
|
@ -219,13 +226,14 @@ fn scrollbar_region(scroll: Px, content_size: Px, control_size: Px) -> Scrollbar
|
|||
}
|
||||
}
|
||||
|
||||
/// The thickness that scrollbars are drawn with.
|
||||
pub struct ScrollBarThickness;
|
||||
|
||||
impl ComponentDefinition for ScrollBarThickness {
|
||||
type ComponentType = Dimension;
|
||||
|
||||
fn default_value(&self) -> Self::ComponentType {
|
||||
Dimension::Lp(Lp::points(9))
|
||||
Dimension::Lp(Lp::points(7))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl<Layers> TileMap<Layers> {
|
|||
fn construct(layers: Value<Layers>) -> Self {
|
||||
Self {
|
||||
layers,
|
||||
focus: Value::Constant(TileMapFocus::default()),
|
||||
focus: Value::default(),
|
||||
zoom: 1.,
|
||||
tick: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,7 +366,8 @@ where
|
|||
) {
|
||||
match state {
|
||||
ElementState::Pressed => {
|
||||
WidgetContext::new(&self.root, &mut window).clear_focus();
|
||||
EventContext::new(WidgetContext::new(&self.root, &mut window), kludgine)
|
||||
.clear_focus();
|
||||
|
||||
if let (ElementState::Pressed, Some(location), Some(hovered)) =
|
||||
(state, &self.mouse_state.location, &self.mouse_state.widget)
|
||||
|
|
|
|||
Loading…
Reference in a new issue