mirror of
https://github.com/danbulant/cushy
synced 2026-06-20 15:01:11 +00:00
Documentation
This commit is contained in:
parent
04e5381187
commit
c9c4c9aeed
26 changed files with 1035 additions and 569 deletions
|
|
@ -1,8 +1,8 @@
|
|||
use gooey::dynamic::Dynamic;
|
||||
use gooey::value::Dynamic;
|
||||
use gooey::widgets::Button;
|
||||
use gooey::{EventLoopError, Run};
|
||||
use gooey::Run;
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
fn main() -> gooey::Result {
|
||||
let count = Dynamic::new(0_usize);
|
||||
Button::new(count.map_each(ToString::to_string))
|
||||
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use gooey::widgets::Canvas;
|
||||
use gooey::Run;
|
||||
use gooey::{Run, Tick};
|
||||
use kludgine::figures::units::Px;
|
||||
use kludgine::figures::{Angle, IntoSigned, Point, Rect, Size};
|
||||
use kludgine::shapes::Shape;
|
||||
|
|
@ -29,6 +29,6 @@ fn main() -> gooey::Result<()> {
|
|||
None,
|
||||
)
|
||||
})
|
||||
.target_fps(60)
|
||||
.tick(Tick::redraws_per_second(60))
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,25 @@
|
|||
use std::string::ToString;
|
||||
|
||||
use gooey::children::Children;
|
||||
use gooey::dynamic::Dynamic;
|
||||
use gooey::value::Dynamic;
|
||||
use gooey::widgets::array::Array;
|
||||
use gooey::widgets::{Button, Label};
|
||||
use gooey::{EventLoopError, Run};
|
||||
use gooey::{widgets, Run};
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
fn main() -> gooey::Result {
|
||||
let counter = Dynamic::new(0i32);
|
||||
let label = counter.map_each(ToString::to_string);
|
||||
Array::rows(
|
||||
Children::new()
|
||||
.with_widget(Label::new(label))
|
||||
.with_widget(Button::new("+").on_click(counter.with_clone(|counter| {
|
||||
move |_| {
|
||||
counter.set(counter.get() + 1);
|
||||
}
|
||||
})))
|
||||
.with_widget(Button::new("-").on_click(counter.with_clone(|counter| {
|
||||
move |_| {
|
||||
counter.set(counter.get() - 1);
|
||||
}
|
||||
}))),
|
||||
)
|
||||
Array::rows(widgets![
|
||||
Label::new(label),
|
||||
Button::new("+").on_click(counter.with_clone(|counter| {
|
||||
move |_| {
|
||||
counter.set(counter.get() + 1);
|
||||
}
|
||||
})),
|
||||
Button::new("-").on_click(counter.with_clone(|counter| {
|
||||
move |_| {
|
||||
counter.set(counter.get() - 1);
|
||||
}
|
||||
})),
|
||||
])
|
||||
.run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use gooey::widgets::Input;
|
||||
use gooey::{EventLoopError, Run};
|
||||
use gooey::Run;
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
fn main() -> gooey::Result {
|
||||
Input::new("Hello").run()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
use gooey::children::Children;
|
||||
use gooey::styles::{Styles, TextColor};
|
||||
use gooey::widget::Widget;
|
||||
use gooey::styles::components::TextColor;
|
||||
use gooey::styles::Styles;
|
||||
use gooey::widget::{Widget, Widgets};
|
||||
use gooey::widgets::array::Array;
|
||||
use gooey::widgets::{Button, Style};
|
||||
use gooey::window::Window;
|
||||
use gooey::{styles, EventLoopError, Run};
|
||||
use gooey::{styles, Run};
|
||||
use kludgine::Color;
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
fn main() -> gooey::Result {
|
||||
Window::for_widget(
|
||||
Array::rows(
|
||||
Children::new()
|
||||
Widgets::new()
|
||||
.with_widget(Button::new("Default"))
|
||||
.with_widget(red_text(Button::new("Styled"))),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use gooey::dynamic::Dynamic;
|
||||
use gooey::kludgine::app::winit::keyboard::Key;
|
||||
use gooey::kludgine::figures::units::Px;
|
||||
use gooey::kludgine::figures::{Point, Rect, Size};
|
||||
|
|
@ -6,9 +5,9 @@ use gooey::kludgine::render::Renderer;
|
|||
use gooey::kludgine::shapes::Shape;
|
||||
use gooey::kludgine::tilemap::{Object, ObjectLayer, TileKind, TileMapFocus, Tiles, TILE_SIZE};
|
||||
use gooey::kludgine::Color;
|
||||
use gooey::tick::Tick;
|
||||
use gooey::value::Dynamic;
|
||||
use gooey::widgets::TileMap;
|
||||
use gooey::{EventLoopError, Run};
|
||||
use gooey::{Run, Tick};
|
||||
|
||||
const PLAYER_SIZE: Px = Px(16);
|
||||
|
||||
|
|
@ -28,7 +27,7 @@ const TILES: [TileKind; 64] = {
|
|||
]
|
||||
};
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
fn main() -> gooey::Result {
|
||||
let mut characters = ObjectLayer::new();
|
||||
|
||||
let myself = characters.push(Player {
|
||||
|
|
@ -43,8 +42,7 @@ fn main() -> Result<(), EventLoopError> {
|
|||
layer: 1,
|
||||
id: myself,
|
||||
})
|
||||
.tick(Tick::fps(60, move |elapsed, input| {
|
||||
// println!("Ticking {input:?}");
|
||||
.tick(Tick::times_per_second(60, move |elapsed, input| {
|
||||
let mut direction = Point::new(0., 0.);
|
||||
if input.keys.contains(&Key::ArrowDown) {
|
||||
direction.y += 1.0;
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use alot::OrderedLots;
|
||||
|
||||
use crate::widget::{BoxedWidget, MakeWidget};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[must_use]
|
||||
pub struct Children {
|
||||
ordered: OrderedLots<BoxedWidget>,
|
||||
}
|
||||
|
||||
impl Children {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ordered: OrderedLots::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_widget<W>(mut self, widget: W) -> Self
|
||||
where
|
||||
W: MakeWidget,
|
||||
{
|
||||
self.ordered.push(widget.make_widget());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.ordered.len()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.ordered.is_empty()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, index: usize) -> Option<&BoxedWidget> {
|
||||
self.ordered.get_by_index(index)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn iter(&self) -> alot::ordered::Iter<'_, BoxedWidget> {
|
||||
self.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Children {
|
||||
type Output = BoxedWidget;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.ordered[index]
|
||||
}
|
||||
}
|
||||
impl IndexMut<usize> for Children {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.ordered[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Children {
|
||||
type IntoIter = alot::ordered::Iter<'a, BoxedWidget>;
|
||||
type Item = &'a BoxedWidget;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.ordered.iter()
|
||||
}
|
||||
}
|
||||
229
src/context.rs
229
src/context.rs
|
|
@ -1,3 +1,4 @@
|
|||
//! Types that provide access to the Gooey runtime.
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use kludgine::app::winit::event::{
|
||||
|
|
@ -8,23 +9,43 @@ use kludgine::figures::{IntoSigned, Point, Rect, Size};
|
|||
use kludgine::shapes::{Shape, StrokeOptions};
|
||||
use kludgine::Kludgine;
|
||||
|
||||
use crate::dynamic::Dynamic;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::{ComponentDefaultvalue, HighlightColor, Styles};
|
||||
use crate::styles::components::HighlightColor;
|
||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
||||
use crate::value::Dynamic;
|
||||
use crate::widget::{BoxedWidget, EventHandling, ManagedWidget};
|
||||
use crate::window::RunningWindow;
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A context to an event function.
|
||||
///
|
||||
/// This type is a combination of a reference to the rendering library,
|
||||
/// [`Kludgine`], and a [`WidgetContext`].
|
||||
pub struct EventContext<'context, 'window> {
|
||||
/// The context for the widget receiving the event.
|
||||
pub widget: WidgetContext<'context, 'window>,
|
||||
/// The rendering library's state.
|
||||
///
|
||||
/// This is useful for accessing the current [scale](Kludgine::scale) or
|
||||
/// information needed to measure and layout text.
|
||||
pub kludgine: &'context mut Kludgine,
|
||||
}
|
||||
|
||||
impl<'context, 'window> EventContext<'context, 'window> {
|
||||
pub fn new(widget: WidgetContext<'context, 'window>, kludgine: &'context mut Kludgine) -> Self {
|
||||
pub(crate) fn new(
|
||||
widget: WidgetContext<'context, 'window>,
|
||||
kludgine: &'context mut Kludgine,
|
||||
) -> Self {
|
||||
Self { widget, kludgine }
|
||||
}
|
||||
|
||||
/// Returns a new `EventContext` with `widget` being referenced in the
|
||||
/// contained [`WidgetContext`].
|
||||
///
|
||||
/// This function is used when one widget contains other widgets, and the
|
||||
/// parent widget needs to invoke events on a child widget. This is done by
|
||||
/// creating an `EventContext` pointing to the child and calling the
|
||||
/// appropriate function to invoke the event.
|
||||
pub fn for_other<'child>(
|
||||
&'child mut self,
|
||||
widget: &'child ManagedWidget,
|
||||
|
|
@ -32,10 +53,14 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
EventContext::new(self.widget.for_other(widget), self.kludgine)
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::hit_test()`](crate::widget::Widget::hit_test) on this
|
||||
/// context's widget and returns the result.
|
||||
pub fn hit_test(&mut self, location: Point<Px>) -> bool {
|
||||
self.current_node.lock().hit_test(location, self)
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::mouse_down()`](crate::widget::Widget::mouse_down) on
|
||||
/// this context's widget and returns the result.
|
||||
pub fn mouse_down(
|
||||
&mut self,
|
||||
location: Point<Px>,
|
||||
|
|
@ -47,12 +72,16 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
.mouse_down(location, device_id, button, self)
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::hit_test()`](crate::widget::Widget::mouse_drag) on
|
||||
/// this context's widget and returns the result.
|
||||
pub fn mouse_drag(&mut self, location: Point<Px>, device_id: DeviceId, button: MouseButton) {
|
||||
self.current_node
|
||||
.lock()
|
||||
.mouse_drag(location, device_id, button, self);
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::mouse_up()`](crate::widget::Widget::mouse_up) on this
|
||||
/// context's widget and returns the result.
|
||||
pub fn mouse_up(
|
||||
&mut self,
|
||||
location: Option<Point<Px>>,
|
||||
|
|
@ -64,6 +93,8 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
.mouse_up(location, device_id, button, self);
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::keyboard_input()`](crate::widget::Widget::keyboard_input) on this
|
||||
/// context's widget and returns the result.
|
||||
pub fn keyboard_input(
|
||||
&mut self,
|
||||
device_id: DeviceId,
|
||||
|
|
@ -75,10 +106,14 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
.keyboard_input(device_id, input, is_synthetic, self)
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::ime()`](crate::widget::Widget::ime) on this
|
||||
/// context's widget and returns the result.
|
||||
pub fn ime(&mut self, ime: Ime) -> EventHandling {
|
||||
self.current_node.lock().ime(ime, self)
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::mouse_wheel()`](crate::widget::Widget::mouse_wheel) on this
|
||||
/// context's widget and returns the result.
|
||||
pub fn mouse_wheel(
|
||||
&mut self,
|
||||
device_id: DeviceId,
|
||||
|
|
@ -112,6 +147,46 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
old_hover.lock().unhover(&mut old_hover_context);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_pending_state(&mut self) {
|
||||
let active = self.pending_state.active.take();
|
||||
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) => {
|
||||
if let Some(old) = old {
|
||||
let mut old_context = self.for_other(&old);
|
||||
old.lock().deactivate(&mut old_context);
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
if new {
|
||||
if let Some(active) = active {
|
||||
active.lock().activate(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let focus = self.pending_state.focus.take();
|
||||
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) => {
|
||||
if let Some(old) = old {
|
||||
let mut old_context = self.for_other(&old);
|
||||
old.lock().blur(&mut old_context);
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
if new {
|
||||
if let Some(focus) = focus {
|
||||
focus.lock().focus(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'context, 'window> Deref for EventContext<'context, 'window> {
|
||||
|
|
@ -128,8 +203,11 @@ impl<'context, 'window> DerefMut for EventContext<'context, 'window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// An owned `T` or an exclusive reference to a `T`.
|
||||
pub enum Exclusive<'a, T> {
|
||||
/// An exclusive borrow.
|
||||
Borrowed(&'a mut T),
|
||||
/// An owned instance.
|
||||
Owned(T),
|
||||
}
|
||||
|
||||
|
|
@ -153,12 +231,19 @@ impl<T> DerefMut for Exclusive<'_, T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A context to a function that is rendering a widget.
|
||||
pub struct GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass> {
|
||||
/// The context of the widget being rendered.
|
||||
pub widget: WidgetContext<'context, 'window>,
|
||||
/// The graphics context clipped and offset to the area of the widget being
|
||||
/// rendered. Drawing at 0,0 will draw at the top-left pixel of the laid-out
|
||||
/// widget region.
|
||||
pub graphics: Exclusive<'context, Graphics<'clip, 'gfx, 'pass>>,
|
||||
}
|
||||
|
||||
impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, 'clip, 'gfx, 'pass> {
|
||||
/// Returns a new `GraphicsContext` that allows invoking graphics functions
|
||||
/// for `widget`.
|
||||
pub fn for_other<'child>(
|
||||
&'child mut self,
|
||||
widget: &'child ManagedWidget,
|
||||
|
|
@ -169,19 +254,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
|
|||
}
|
||||
}
|
||||
|
||||
pub fn measure(&mut self, available_space: Size<ConstraintLimit>) -> Size<UPx> {
|
||||
self.current_node.lock().measure(available_space, self)
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self) {
|
||||
// TODO this should not use clip_rect, because it forces UPx, and once
|
||||
// we have scrolling, we can have negative offsets of rectangles where
|
||||
// it's clipped partially.
|
||||
self.current_node
|
||||
.note_rendered_rect(self.graphics.clip_rect().into_signed());
|
||||
self.current_node.lock().redraw(self);
|
||||
}
|
||||
|
||||
/// Returns a new graphics context that renders to the `clip` rectangle.
|
||||
pub fn clipped_to(&mut self, clip: Rect<UPx>) -> GraphicsContext<'_, 'window, '_, 'gfx, 'pass> {
|
||||
GraphicsContext {
|
||||
widget: self.widget.borrowed(),
|
||||
|
|
@ -189,7 +262,11 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw_focus_ring(&mut self, styles: &Styles) {
|
||||
/// Renders the default focus ring for this widget.
|
||||
///
|
||||
/// To ensure the correct color is used, include [`HighlightColor`] in the
|
||||
/// styles request.
|
||||
pub fn draw_focus_ring_using(&mut self, styles: &Styles) {
|
||||
let visible_rect = Rect::from(self.graphics.size() - (UPx(1), UPx(1)));
|
||||
let focus_ring = Shape::stroked_rect(
|
||||
visible_rect,
|
||||
|
|
@ -199,6 +276,28 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
|
|||
self.graphics
|
||||
.draw_shape(&focus_ring, Point::default(), None, None);
|
||||
}
|
||||
|
||||
/// Renders the default focus ring for this widget.
|
||||
pub fn draw_focus_ring(&mut self) {
|
||||
self.draw_focus_ring_using(&self.query_style(&[&HighlightColor]));
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::measure()`](crate::widget::Widget::measure) on this
|
||||
/// context's widget and returns the result.
|
||||
pub fn measure(&mut self, available_space: Size<ConstraintLimit>) -> Size<UPx> {
|
||||
self.current_node.lock().measure(available_space, self)
|
||||
}
|
||||
|
||||
/// Invokes [`Widget::redraw()`](crate::widget::Widget::redraw) on this
|
||||
/// context's widget.
|
||||
pub fn redraw(&mut self) {
|
||||
// TODO this should not use clip_rect, because it forces UPx, and once
|
||||
// we have scrolling, we can have negative offsets of rectangles where
|
||||
// it's clipped partially.
|
||||
self.current_node
|
||||
.note_rendered_rect(self.graphics.clip_rect().into_signed());
|
||||
self.current_node.lock().redraw(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'context, 'window, 'clip, 'gfx, 'pass> Deref
|
||||
|
|
@ -219,9 +318,13 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> DerefMut
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts from one context to an [`EventContext`].
|
||||
pub trait AsEventContext<'window> {
|
||||
/// Returns this context as an [`EventContext`].
|
||||
fn as_event_context(&mut self) -> EventContext<'_, 'window>;
|
||||
|
||||
/// Pushes a new child widget into the widget hierarchy beneathq the
|
||||
/// context's widget.
|
||||
#[must_use]
|
||||
fn push_child(&mut self, child: BoxedWidget) -> ManagedWidget {
|
||||
let mut context = self.as_event_context();
|
||||
|
|
@ -235,6 +338,7 @@ pub trait AsEventContext<'window> {
|
|||
pushed_widget
|
||||
}
|
||||
|
||||
/// Removes a widget from the hierarchy.
|
||||
fn remove_child(&mut self, child: &ManagedWidget) {
|
||||
let mut context = self.as_event_context();
|
||||
context
|
||||
|
|
@ -243,47 +347,6 @@ pub trait AsEventContext<'window> {
|
|||
.remove_child(child, context.current_node);
|
||||
child.lock().unmounted(&mut context.for_other(child));
|
||||
}
|
||||
|
||||
fn apply_pending_state(&mut self) {
|
||||
let mut context = self.as_event_context();
|
||||
let active = context.pending_state.active.take();
|
||||
if context.current_node.tree.active_widget() != active.as_ref().map(|active| active.id) {
|
||||
let new = match context.current_node.tree.activate(active.as_ref()) {
|
||||
Ok(old) => {
|
||||
if let Some(old) = old {
|
||||
let mut old_context = context.for_other(&old);
|
||||
old.lock().deactivate(&mut old_context);
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
if new {
|
||||
if let Some(active) = active {
|
||||
active.lock().activate(&mut context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let focus = context.pending_state.focus.take();
|
||||
if context.current_node.tree.focused_widget() != focus.as_ref().map(|focus| focus.id) {
|
||||
let new = match context.current_node.tree.focus(focus.as_ref()) {
|
||||
Ok(old) => {
|
||||
if let Some(old) = old {
|
||||
let mut old_context = context.for_other(&old);
|
||||
old.lock().blur(&mut old_context);
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
if new {
|
||||
if let Some(focus) = focus {
|
||||
focus.lock().focus(&mut context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'window> AsEventContext<'window> for EventContext<'_, 'window> {
|
||||
|
|
@ -298,6 +361,10 @@ impl<'window> AsEventContext<'window> for GraphicsContext<'_, 'window, '_, '_, '
|
|||
}
|
||||
}
|
||||
|
||||
/// A context for a widget.
|
||||
///
|
||||
/// This type provides access to the widget hierarchy from the perspective of a
|
||||
/// specific widget.
|
||||
pub struct WidgetContext<'context, 'window> {
|
||||
current_node: &'context ManagedWidget,
|
||||
window: &'context mut RunningWindow<'window>,
|
||||
|
|
@ -325,6 +392,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new instance that borrows from `self`.
|
||||
pub fn borrowed(&mut self) -> WidgetContext<'_, 'window> {
|
||||
WidgetContext {
|
||||
current_node: self.current_node,
|
||||
|
|
@ -333,6 +401,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new context representing `widget`.
|
||||
pub fn for_other<'child>(
|
||||
&'child mut self,
|
||||
widget: &'child ManagedWidget,
|
||||
|
|
@ -348,15 +417,21 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
self.current_node.parent()
|
||||
}
|
||||
|
||||
/// Ensures that this widget will be redrawn when `value` has been updated.
|
||||
pub fn redraw_when_changed<T>(&self, value: &Dynamic<T>) {
|
||||
value.redraw_when_changed(self.window.handle());
|
||||
}
|
||||
|
||||
/// Returns the region that this widget last rendered at.
|
||||
#[must_use]
|
||||
pub fn last_rendered_at(&self) -> Option<Rect<Px>> {
|
||||
self.current_node.last_rendered_at()
|
||||
}
|
||||
|
||||
/// Sets the currently focused widget to this widget.
|
||||
///
|
||||
/// Widget events relating to focus changes are deferred until after the all
|
||||
/// contexts for the currently firing event are dropped.
|
||||
pub fn focus(&mut self) {
|
||||
self.pending_state.focus = Some(self.current_node.clone());
|
||||
}
|
||||
|
|
@ -365,6 +440,12 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
self.pending_state.focus = None;
|
||||
}
|
||||
|
||||
/// Clears focus from this widget, if it is the focused widget.
|
||||
///
|
||||
/// Returns true if this function resulted in the focus being changed.
|
||||
///
|
||||
/// Widget events relating to focus changes are deferred until after the all
|
||||
/// contexts for the currently firing event are dropped.
|
||||
pub fn blur(&mut self) -> bool {
|
||||
if self.focused() {
|
||||
self.clear_focus();
|
||||
|
|
@ -374,6 +455,13 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Activates this widget, if it is not already active.
|
||||
///
|
||||
/// Returns true if this function resulted in the currently active widget
|
||||
/// being changed.
|
||||
///
|
||||
/// Widget events relating to activation changes are deferred until after
|
||||
/// the all contexts for the currently firing event are dropped.
|
||||
pub fn activate(&mut self) -> bool {
|
||||
if self
|
||||
.pending_state
|
||||
|
|
@ -388,6 +476,13 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Deactivates this widget, if it is the currently active widget.
|
||||
///
|
||||
/// Returns true if this function resulted in the active widget being
|
||||
/// changed.
|
||||
///
|
||||
/// Widget events relating to activation changes are deferred until after
|
||||
/// the all contexts for the currently firing event are dropped.
|
||||
pub fn deactivate(&mut self) -> bool {
|
||||
if self.active() {
|
||||
self.clear_active();
|
||||
|
|
@ -401,30 +496,48 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
self.pending_state.active = None;
|
||||
}
|
||||
|
||||
/// Returns true if this widget is currently the active widget.
|
||||
#[must_use]
|
||||
pub fn active(&self) -> bool {
|
||||
self.pending_state.active.as_ref() == Some(self.current_node)
|
||||
}
|
||||
|
||||
/// Returns true if this widget is currently hovered.
|
||||
#[must_use]
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.current_node.hovered()
|
||||
}
|
||||
|
||||
/// Returns true if this widget is currently focused for user input.
|
||||
#[must_use]
|
||||
pub fn focused(&self) -> bool {
|
||||
self.pending_state.focus.as_ref() == Some(self.current_node)
|
||||
}
|
||||
|
||||
/// Returns the widget this context is for.
|
||||
#[must_use]
|
||||
pub const fn widget(&self) -> &ManagedWidget {
|
||||
self.current_node
|
||||
}
|
||||
|
||||
/// Attaches `styles` to the widget hierarchy for this widget.
|
||||
///
|
||||
/// Style queries for children will return any values matching this
|
||||
/// collection.
|
||||
pub fn attach_styles(&self, styles: Styles) {
|
||||
self.current_node.attach_styles(styles);
|
||||
}
|
||||
|
||||
/// Queries the widget hierarchy for matching style components.
|
||||
///
|
||||
/// This function traverses up the widget hierarchy looking for the
|
||||
/// components being requested. The resulting styles will contain the values
|
||||
/// from the closest matches in the widget hierarchy.
|
||||
///
|
||||
/// For style components to be found, they must have previously been
|
||||
/// [attached](Self::attach_styles). The [`Style`](crate::widgets::Style)
|
||||
/// 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 {
|
||||
self.current_node.tree.query_style(self.current_node, query)
|
||||
|
|
|
|||
|
|
@ -2,24 +2,36 @@ use std::ops::{Deref, DerefMut};
|
|||
|
||||
use kludgine::figures::units::UPx;
|
||||
use kludgine::figures::Rect;
|
||||
use kludgine::render::Renderer;
|
||||
use kludgine::ClipGuard;
|
||||
|
||||
/// A 2d graphics context
|
||||
pub struct Graphics<'clip, 'gfx, 'pass> {
|
||||
renderer: RenderContext<'clip, 'gfx, 'pass>,
|
||||
}
|
||||
|
||||
enum RenderContext<'clip, 'gfx, 'pass> {
|
||||
Renderer(kludgine::render::Renderer<'gfx, 'pass>),
|
||||
Clipped(kludgine::ClipGuard<'clip, kludgine::render::Renderer<'gfx, 'pass>>),
|
||||
Renderer(Renderer<'gfx, 'pass>),
|
||||
Clipped(ClipGuard<'clip, Renderer<'gfx, 'pass>>),
|
||||
}
|
||||
|
||||
impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
||||
/// Returns a new graphics context for the given [`Renderer`].
|
||||
#[must_use]
|
||||
pub fn new(renderer: kludgine::render::Renderer<'gfx, 'pass>) -> Self {
|
||||
pub fn new(renderer: Renderer<'gfx, 'pass>) -> Self {
|
||||
Self {
|
||||
renderer: RenderContext::Renderer(renderer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a context that has been clipped to `clip`.
|
||||
///
|
||||
/// The new clipping rectangle is interpreted relative to the current
|
||||
/// clipping rectangle. As a side effect, this function can never expand the
|
||||
/// clipping rect beyond the current clipping rect.
|
||||
///
|
||||
/// The returned context will report the clipped size, and all drawing
|
||||
/// operations will be relative to the origin of `clip`.
|
||||
pub fn clipped_to(&mut self, clip: Rect<UPx>) -> Graphics<'_, 'gfx, 'pass> {
|
||||
Graphics {
|
||||
renderer: RenderContext::Clipped(self.deref_mut().clipped_to(clip)),
|
||||
|
|
@ -28,7 +40,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
}
|
||||
|
||||
impl<'gfx, 'pass> Deref for Graphics<'_, 'gfx, 'pass> {
|
||||
type Target = kludgine::render::Renderer<'gfx, 'pass>;
|
||||
type Target = Renderer<'gfx, 'pass>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match &self.renderer {
|
||||
|
|
|
|||
84
src/lib.rs
84
src/lib.rs
|
|
@ -1,35 +1,38 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
#![allow(
|
||||
clippy::module_name_repetitions,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc
|
||||
)]
|
||||
//! A reactive, `wgpu`-based Graphical User Interface (GUI) crate for Rust.
|
||||
#![warn(clippy::pedantic, missing_docs)]
|
||||
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
|
||||
|
||||
pub mod children;
|
||||
pub mod context;
|
||||
pub mod dynamic;
|
||||
pub mod graphics;
|
||||
pub mod names;
|
||||
mod graphics;
|
||||
mod names;
|
||||
pub mod styles;
|
||||
pub mod tick;
|
||||
mod tick;
|
||||
mod tree;
|
||||
mod utils;
|
||||
pub mod value;
|
||||
pub mod widget;
|
||||
pub mod widgets;
|
||||
pub mod window;
|
||||
|
||||
pub use kludgine;
|
||||
pub use kludgine::app::winit::error::EventLoopError;
|
||||
pub use kludgine::app::winit::event::ElementState;
|
||||
use kludgine::app::winit::error::EventLoopError;
|
||||
use kludgine::figures::units::UPx;
|
||||
pub use names::Name;
|
||||
|
||||
pub use self::graphics::Graphics;
|
||||
pub use self::tick::{InputState, Tick};
|
||||
|
||||
/// A limit used when measuring a widget.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ConstraintLimit {
|
||||
/// The widget is expected to occupy a known size.
|
||||
Known(UPx),
|
||||
/// The widget is expected to resize itself to fit within the size provided.
|
||||
ClippedAfter(UPx),
|
||||
}
|
||||
|
||||
impl ConstraintLimit {
|
||||
/// Returns the maximum measurement that will fit the constraint.
|
||||
#[must_use]
|
||||
pub fn max(self) -> UPx {
|
||||
match self {
|
||||
|
|
@ -38,8 +41,59 @@ impl ConstraintLimit {
|
|||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = EventLoopError> = std::result::Result<T, E>;
|
||||
/// A result alias that defaults to the result type commonly used throughout
|
||||
/// this crate.
|
||||
pub type Result<T = (), E = EventLoopError> = std::result::Result<T, E>;
|
||||
|
||||
/// A type that can be run as an application.
|
||||
pub trait Run: Sized {
|
||||
fn run(self) -> Result<(), EventLoopError>;
|
||||
/// Runs the provided type, returning `Ok(())` upon successful execution and
|
||||
/// program exit. Note that this function may not ever return on some
|
||||
/// platforms.
|
||||
fn run(self) -> crate::Result;
|
||||
}
|
||||
|
||||
/// Creates a [`Widgets`](crate::widget::Widgets) instance with the given list
|
||||
/// of widgets.
|
||||
#[macro_export]
|
||||
macro_rules! widgets {
|
||||
() => {
|
||||
$crate::widget::Widgets::new()
|
||||
};
|
||||
($($widget:expr),+) => {{
|
||||
let mut widgets = $crate::widget::Widgets::with_capacity($crate::count!($($widget),+ ;));
|
||||
$(widgets.push($widget);)+
|
||||
widgets
|
||||
}};
|
||||
($($widget:expr),+ ,) => {{
|
||||
$crate::widgets!($($widget),+)
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! count {
|
||||
($value:expr ;) => {
|
||||
1
|
||||
};
|
||||
($value:expr , $($remaining:expr),+ ;) => {
|
||||
1 + $crate::count!($($remaining),+ ;)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Styles`](crate::styles::Styles) instance with the given
|
||||
/// name/component pairs.
|
||||
#[macro_export]
|
||||
macro_rules! styles {
|
||||
() => {{
|
||||
$crate::styles::Styles::new()
|
||||
}};
|
||||
($($component:expr => $value:expr),*) => {{
|
||||
let mut styles = $crate::styles::Styles::with_capacity($crate::count!($($value),* ;));
|
||||
$(styles.insert(&$component, $value);)*
|
||||
styles
|
||||
}};
|
||||
($($component:expr => $value:expr),* ,) => {{
|
||||
$crate::styles!($($component => $value),*)
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,17 @@ use interner::global::{GlobalString, StringPool};
|
|||
|
||||
static NAMES: StringPool = StringPool::new();
|
||||
|
||||
/// A smart-string type that is used as a "name" in Gooey.
|
||||
///
|
||||
/// This type ensures that globably only one instance of any unique wrapped
|
||||
/// string exists. By ensuring all instances of each unique string are the same
|
||||
/// exact underlying instance, optimizations can be made that avoid string
|
||||
/// comparisons.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Name(GlobalString);
|
||||
|
||||
impl Name {
|
||||
/// Returns a name for the given string.
|
||||
pub fn new<'a>(name: impl Into<Cow<'a, str>>) -> Self {
|
||||
Self(NAMES.get(name))
|
||||
}
|
||||
|
|
|
|||
160
src/styles.rs
160
src/styles.rs
|
|
@ -1,3 +1,5 @@
|
|||
//! Types for styling widgets.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{hash_map, HashMap};
|
||||
use std::sync::Arc;
|
||||
|
|
@ -5,64 +7,48 @@ use std::sync::Arc;
|
|||
use crate::names::Name;
|
||||
use crate::utils::Lazy;
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! count {
|
||||
($value:expr ;) => {
|
||||
1
|
||||
};
|
||||
($value:expr , $($remaining:expr),+ ;) => {
|
||||
1 + count!($($remaining),+ ;)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! styles {
|
||||
() => {{
|
||||
$crate::styles::Styles::new()
|
||||
}};
|
||||
($($component:expr => $value:expr),*) => {{
|
||||
let mut styles = $crate::styles::Styles::with_capacity($crate::count!($($value),* ;));
|
||||
$(styles.push(&$component, $value);)*
|
||||
styles
|
||||
}};
|
||||
($($component:expr => $value:expr),* ,) => {{
|
||||
$crate::styles!($($component => $value),*)
|
||||
}};
|
||||
}
|
||||
pub mod components;
|
||||
|
||||
/// A collection of style components organized by their name.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Styles(Arc<HashMap<Group, HashMap<Name, Component>>>);
|
||||
|
||||
impl Styles {
|
||||
/// Returns an empty collection.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Returns a collection with the capacity to hold up to `capacity` elements
|
||||
/// without reallocating.
|
||||
#[must_use]
|
||||
pub fn with_capacity(components: usize) -> Self {
|
||||
Self(Arc::new(HashMap::with_capacity(components)))
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self(Arc::new(HashMap::with_capacity(capacity)))
|
||||
}
|
||||
|
||||
pub fn push_component(&mut self, name: ComponentName, component: impl Into<Component>) {
|
||||
/// Inserts a [`Component`] with a given name.
|
||||
pub fn insert_named(&mut self, name: ComponentName, component: impl Into<Component>) {
|
||||
Arc::make_mut(&mut self.0)
|
||||
.entry(name.group)
|
||||
.or_default()
|
||||
.insert(name.name, component.into());
|
||||
}
|
||||
|
||||
pub fn push(&mut self, name: &impl NamedComponent, component: impl Into<Component>) {
|
||||
/// Inserts a [`Component`] using then name provided.
|
||||
pub fn insert(&mut self, name: &impl NamedComponent, component: impl Into<Component>) {
|
||||
let name = name.name().into_owned();
|
||||
self.push_component(name, component);
|
||||
self.insert_named(name, component);
|
||||
}
|
||||
|
||||
/// Adds a [`Component`] for the name provided and returns self.
|
||||
#[must_use]
|
||||
pub fn with(mut self, name: &impl NamedComponent, component: impl Into<Component>) -> Self {
|
||||
self.push(name, component);
|
||||
self.insert(name, component);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the associated component for the given name, if found.
|
||||
#[must_use]
|
||||
pub fn get<Named>(&self, component: &Named) -> Option<&Component>
|
||||
where
|
||||
|
|
@ -74,6 +60,8 @@ impl Styles {
|
|||
.and_then(|group| group.get(&name.name))
|
||||
}
|
||||
|
||||
/// Returns the component associated with the given name, or if not found,
|
||||
/// returns the default value provided by the definition.
|
||||
#[must_use]
|
||||
pub fn get_or_default<Named>(&self, component: &Named) -> Named::ComponentType
|
||||
where
|
||||
|
|
@ -93,7 +81,7 @@ impl FromIterator<(ComponentName, Component)> for Styles {
|
|||
let iter = iter.into_iter();
|
||||
let mut styles = Self::with_capacity(iter.size_hint().0);
|
||||
for (name, component) in iter {
|
||||
styles.push_component(name, component);
|
||||
styles.insert_named(name, component);
|
||||
}
|
||||
styles
|
||||
}
|
||||
|
|
@ -113,6 +101,7 @@ impl IntoIterator for Styles {
|
|||
}
|
||||
}
|
||||
|
||||
/// An iterator over the owned contents of a [`Styles`] instance.
|
||||
pub struct StylesIntoIter {
|
||||
main: hash_map::IntoIter<Group, HashMap<Name, Component>>,
|
||||
names: Option<(Group, hash_map::IntoIter<Name, Component>)>,
|
||||
|
|
@ -136,7 +125,6 @@ impl Iterator for StylesIntoIter {
|
|||
}
|
||||
}
|
||||
|
||||
pub type StyleQuery = Vec<ComponentName>;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
|
@ -145,12 +133,17 @@ use kludgine::figures::units::{Lp, Px};
|
|||
use kludgine::figures::ScreenScale;
|
||||
use kludgine::Color;
|
||||
|
||||
/// A value of a style component.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Component {
|
||||
/// A color.
|
||||
Color(Color),
|
||||
/// A single-dimension measurement.
|
||||
Dimension(Dimension),
|
||||
/// A percentage between 0.0 and 1.0.
|
||||
Percent(f32),
|
||||
Boxed(BoxedComponent),
|
||||
/// A custom component type.
|
||||
Boxed(CustomComponent),
|
||||
}
|
||||
|
||||
impl From<Color> for Component {
|
||||
|
|
@ -221,9 +214,12 @@ impl TryFrom<Component> for Lp {
|
|||
}
|
||||
}
|
||||
|
||||
/// A 1-dimensional measurement.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Dimension {
|
||||
/// Physical Pixels
|
||||
Px(Px),
|
||||
/// Logical Pixels
|
||||
Lp(Lp),
|
||||
}
|
||||
|
||||
|
|
@ -266,10 +262,12 @@ impl ScreenScale for Dimension {
|
|||
}
|
||||
}
|
||||
|
||||
/// A custom component value.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoxedComponent(Arc<dyn AnyComponent>);
|
||||
pub struct CustomComponent(Arc<dyn AnyComponent>);
|
||||
|
||||
impl BoxedComponent {
|
||||
impl CustomComponent {
|
||||
/// Wraps an arbitrary value so that it can be used as a [`Component`].
|
||||
pub fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: RefUnwindSafe + UnwindSafe + Debug + Send + Sync + 'static,
|
||||
|
|
@ -277,6 +275,8 @@ impl BoxedComponent {
|
|||
Self(Arc::new(value))
|
||||
}
|
||||
|
||||
/// Return the contained value cast as `T`. Returns `None` if `T` does is
|
||||
/// not the same type that was provided when this component was created.
|
||||
#[must_use]
|
||||
pub fn downcast<T>(&self) -> Option<&T>
|
||||
where
|
||||
|
|
@ -299,10 +299,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A style component group.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Group(Name);
|
||||
|
||||
impl Group {
|
||||
/// Returns a new instance using the group name of `T`.
|
||||
#[must_use]
|
||||
pub fn new<T>() -> Self
|
||||
where
|
||||
|
|
@ -311,6 +313,7 @@ impl Group {
|
|||
Self(T::name())
|
||||
}
|
||||
|
||||
/// Returns true if this instance matches the group name of `T`.
|
||||
#[must_use]
|
||||
pub fn matches<T>(&self) -> bool
|
||||
where
|
||||
|
|
@ -320,10 +323,13 @@ impl Group {
|
|||
}
|
||||
}
|
||||
|
||||
/// A type that represents a group of style components.
|
||||
pub trait ComponentGroup {
|
||||
/// Returns the name of the group.
|
||||
fn name() -> Name;
|
||||
}
|
||||
|
||||
/// The Global style components group.
|
||||
pub enum Global {}
|
||||
|
||||
impl ComponentGroup for Global {
|
||||
|
|
@ -332,13 +338,17 @@ impl ComponentGroup for Global {
|
|||
}
|
||||
}
|
||||
|
||||
/// A fully-qualified style component name.
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct ComponentName {
|
||||
/// The group name.
|
||||
pub group: Group,
|
||||
/// The name of the component within the group.
|
||||
pub name: Name,
|
||||
}
|
||||
|
||||
impl ComponentName {
|
||||
/// Returns a new instance using `group` and `name`.
|
||||
pub fn new(group: Group, name: impl Into<Name>) -> Self {
|
||||
Self {
|
||||
group,
|
||||
|
|
@ -346,6 +356,7 @@ impl ComponentName {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new instance using `G` and `name`.
|
||||
pub fn named<G: ComponentGroup>(name: impl Into<Name>) -> Self {
|
||||
Self::new(Group::new::<G>(), name)
|
||||
}
|
||||
|
|
@ -356,17 +367,26 @@ impl From<&'static Lazy<ComponentName>> for ComponentName {
|
|||
(**value).clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that represents a named style component.
|
||||
pub trait NamedComponent {
|
||||
/// Returns the name of the style component.
|
||||
fn name(&self) -> Cow<'_, ComponentName>;
|
||||
}
|
||||
|
||||
/// A type that represents a named component with a default value of a specific
|
||||
/// Rust type.
|
||||
pub trait ComponentDefinition: NamedComponent {
|
||||
/// The type that will be contained in the [`Component`].
|
||||
type ComponentType: Into<Component> + TryFrom<Component, Error = Component>;
|
||||
|
||||
/// Returns the default value to use for this component.
|
||||
fn default_value(&self) -> Self::ComponentType;
|
||||
}
|
||||
|
||||
/// A type that represents a named component with a default value.
|
||||
pub trait ComponentDefaultvalue: NamedComponent {
|
||||
/// Returns the default value for this component.
|
||||
fn default_component_value(&self) -> Component;
|
||||
}
|
||||
|
||||
|
|
@ -390,71 +410,3 @@ impl NamedComponent for Cow<'_, ComponentName> {
|
|||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct TextSize;
|
||||
|
||||
impl NamedComponent for TextSize {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("text_size"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for TextSize {
|
||||
type ComponentType = Dimension;
|
||||
|
||||
fn default_value(&self) -> Dimension {
|
||||
Dimension::Lp(Lp::points(12))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct LineHeight;
|
||||
|
||||
impl NamedComponent for LineHeight {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("line_height"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for LineHeight {
|
||||
type ComponentType = Dimension;
|
||||
|
||||
fn default_value(&self) -> Dimension {
|
||||
Dimension::Lp(Lp::points(14))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct TextColor;
|
||||
|
||||
impl NamedComponent for TextColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("text_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for TextColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::WHITE
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct HighlightColor;
|
||||
|
||||
impl NamedComponent for HighlightColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("highlight_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for HighlightColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::AQUA
|
||||
}
|
||||
}
|
||||
|
|
|
|||
79
src/styles/components.rs
Normal file
79
src/styles/components.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
//! All style components supported by the built-in widgets.
|
||||
use std::borrow::Cow;
|
||||
|
||||
use kludgine::figures::units::Lp;
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::styles::{ComponentDefinition, ComponentName, Dimension, Global, NamedComponent};
|
||||
|
||||
/// The [`Dimension`] to use as the size to render text.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct TextSize;
|
||||
|
||||
impl NamedComponent for TextSize {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("text_size"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for TextSize {
|
||||
type ComponentType = Dimension;
|
||||
|
||||
fn default_value(&self) -> Dimension {
|
||||
Dimension::Lp(Lp::points(12))
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`Dimension`] to use to space multiple lines of text.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct LineHeight;
|
||||
|
||||
impl NamedComponent for LineHeight {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("line_height"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for LineHeight {
|
||||
type ComponentType = Dimension;
|
||||
|
||||
fn default_value(&self) -> Dimension {
|
||||
Dimension::Lp(Lp::points(14))
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`Color`] to use when rendering text.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct TextColor;
|
||||
|
||||
impl NamedComponent for TextColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("text_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for TextColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::WHITE
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Color`] to be used as a highlight color.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct HighlightColor;
|
||||
|
||||
impl NamedComponent for HighlightColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("highlight_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for HighlightColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::AQUA
|
||||
}
|
||||
}
|
||||
135
src/tick.rs
135
src/tick.rs
|
|
@ -6,8 +6,12 @@ use std::time::{Duration, Instant};
|
|||
use kludgine::app::winit::event::KeyEvent;
|
||||
use kludgine::app::winit::keyboard::Key;
|
||||
|
||||
use crate::widget::{EventHandling, HANDLED, UNHANDLED};
|
||||
use crate::context::WidgetContext;
|
||||
use crate::value::Dynamic;
|
||||
use crate::widget::{EventHandling, HANDLED, IGNORED};
|
||||
|
||||
/// A fixed-rate callback that provides access to tracked input on its
|
||||
/// associated widget.
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub struct Tick {
|
||||
|
|
@ -16,12 +20,18 @@ pub struct Tick {
|
|||
}
|
||||
|
||||
impl Tick {
|
||||
pub fn rendered(&self) {
|
||||
self.data.rendered_frame.fetch_add(1, Ordering::AcqRel);
|
||||
/// Signals that this widget has been redrawn.
|
||||
pub fn rendered(&self, context: &WidgetContext<'_, '_>) {
|
||||
context.redraw_when_changed(&self.data.tick_number);
|
||||
|
||||
self.data.sync.notify_one();
|
||||
}
|
||||
|
||||
/// Processes `input`.
|
||||
///
|
||||
/// If the event matches a key that has been marked as
|
||||
/// handled, [`HANDLED`] will be returned. Otherwise, [`UNHANDLED`] will be
|
||||
/// returned,
|
||||
#[must_use]
|
||||
pub fn key_input(&self, input: &KeyEvent) -> EventHandling {
|
||||
let mut state = self.data.state();
|
||||
|
|
@ -35,13 +45,71 @@ impl Tick {
|
|||
if self.handled_keys.contains(&input.logical_key) {
|
||||
HANDLED
|
||||
} else {
|
||||
UNHANDLED
|
||||
IGNORED
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new tick that invokes `tick`, aiming to repeat at the given
|
||||
/// duration.
|
||||
pub fn new<F>(tick_every: Duration, tick: F) -> Self
|
||||
where
|
||||
F: FnMut(Duration, &InputState) + Send + 'static,
|
||||
{
|
||||
let now = Instant::now();
|
||||
let data = Arc::new(TickData {
|
||||
state: Mutex::new(TickState {
|
||||
last_time: now,
|
||||
next_target: now,
|
||||
keep_running: true,
|
||||
frame: 0,
|
||||
input: InputState::default(),
|
||||
}),
|
||||
period: tick_every,
|
||||
sync: Condvar::new(),
|
||||
rendered_frame: AtomicUsize::new(0),
|
||||
tick_number: Dynamic::default(),
|
||||
});
|
||||
|
||||
std::thread::spawn({
|
||||
let data = data.clone();
|
||||
move || tick_loop(&data, tick)
|
||||
});
|
||||
|
||||
Self {
|
||||
data,
|
||||
handled_keys: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new tick that invokes `tick` at a target number of times per
|
||||
/// second.
|
||||
pub fn times_per_second<F>(times_per_second: u32, tick: F) -> Self
|
||||
where
|
||||
F: FnMut(Duration, &InputState) + Send + 'static,
|
||||
{
|
||||
Self::new(Duration::from_secs(1) / times_per_second, tick)
|
||||
}
|
||||
|
||||
/// Returns a new tick that redraws its associated widget at a target rate
|
||||
/// of `x times_per_second`.
|
||||
pub fn redraws_per_second(times_per_second: u32) -> Self {
|
||||
Self::times_per_second(times_per_second, |_, _| {})
|
||||
}
|
||||
|
||||
/// Adds the collection of [`Key`]s to the list that are handled, and
|
||||
/// returns self.
|
||||
///
|
||||
/// The list of keys provided will be prevented from propagating.
|
||||
pub fn handled_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {
|
||||
self.handled_keys.extend(keys);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of input during the execution of a [`Tick`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WatchedInput {
|
||||
pub struct InputState {
|
||||
/// A collection of all keys currently pressed.
|
||||
pub keys: HashSet<Key>,
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +119,7 @@ struct TickData {
|
|||
period: Duration,
|
||||
sync: Condvar,
|
||||
rendered_frame: AtomicUsize,
|
||||
tick_number: Dynamic<u64>,
|
||||
}
|
||||
|
||||
impl TickData {
|
||||
|
|
@ -67,55 +136,12 @@ struct TickState {
|
|||
next_target: Instant,
|
||||
keep_running: bool,
|
||||
frame: usize,
|
||||
input: WatchedInput,
|
||||
}
|
||||
|
||||
impl Tick {
|
||||
pub fn new<F>(tick_every: Duration, tick: F) -> Self
|
||||
where
|
||||
F: FnMut(Duration, &WatchedInput) + Send + 'static,
|
||||
{
|
||||
let now = Instant::now();
|
||||
let data = Arc::new(TickData {
|
||||
state: Mutex::new(TickState {
|
||||
last_time: now,
|
||||
next_target: now,
|
||||
keep_running: true,
|
||||
frame: 0,
|
||||
input: WatchedInput::default(),
|
||||
}),
|
||||
period: tick_every,
|
||||
sync: Condvar::new(),
|
||||
rendered_frame: AtomicUsize::new(0),
|
||||
});
|
||||
|
||||
std::thread::spawn({
|
||||
let data = data.clone();
|
||||
move || tick_loop(&data, tick)
|
||||
});
|
||||
|
||||
Self {
|
||||
data,
|
||||
handled_keys: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fps<F>(frames_per_second: u32, tick: F) -> Self
|
||||
where
|
||||
F: FnMut(Duration, &WatchedInput) + Send + 'static,
|
||||
{
|
||||
Self::new(Duration::from_secs(1) / frames_per_second, tick)
|
||||
}
|
||||
|
||||
pub fn handled_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {
|
||||
self.handled_keys.extend(keys);
|
||||
self
|
||||
}
|
||||
input: InputState,
|
||||
}
|
||||
|
||||
fn tick_loop<F>(data: &TickData, mut tick: F)
|
||||
where
|
||||
F: FnMut(Duration, &WatchedInput),
|
||||
F: FnMut(Duration, &InputState),
|
||||
{
|
||||
let mut state = data.state();
|
||||
while state.keep_running {
|
||||
|
|
@ -135,14 +161,15 @@ where
|
|||
.checked_duration_since(state.last_time)
|
||||
.expect("instant never decreases");
|
||||
state.frame += 1;
|
||||
// TODO we need a way to batch updates for a context so that during a
|
||||
// tick, no changed values trigger a redraw until we are done with the
|
||||
// tick. Otherwise, a frame may start being rendered while we're still
|
||||
// evaluating the tick since it's in its own thread.
|
||||
|
||||
tick(elapsed, &state.input);
|
||||
state.next_target = (state.next_target + data.period).max(now);
|
||||
state.last_time = now;
|
||||
|
||||
// Signal that we have a new frame, which will cause the widget to
|
||||
// redraw.
|
||||
data.tick_number.map_mut(|tick| *tick += 1);
|
||||
|
||||
// Wait for a frame to be rendered.
|
||||
while state.keep_running {
|
||||
let current_frame = data.rendered_frame.load(Ordering::Acquire);
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ impl TreeData {
|
|||
if let Some(styles) = &node.styles {
|
||||
query.retain(|name| {
|
||||
if let Some(component) = styles.get(name) {
|
||||
resolved.push(name, component.clone());
|
||||
resolved.insert(name, component.clone());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
//! Types for storing and interacting with values in Widgets.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
|
||||
|
||||
use kludgine::app::WindowHandle;
|
||||
|
||||
use crate::context::WidgetContext;
|
||||
use crate::window::sealed::WindowCommand;
|
||||
|
||||
/// An instance of a value that provides APIs to observe and react to its
|
||||
/// contents.
|
||||
#[derive(Debug)]
|
||||
pub struct Dynamic<T>(Arc<DynamicData<T>>);
|
||||
|
||||
impl<T> Dynamic<T> {
|
||||
/// Creates a new instance wrapping `value`.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(Arc::new(DynamicData {
|
||||
state: Mutex::new(State {
|
||||
wrapped: GenerationalValue {
|
||||
value,
|
||||
generation: 0,
|
||||
generation: Generation::default(),
|
||||
},
|
||||
callbacks: Vec::new(),
|
||||
windows: Vec::new(),
|
||||
|
|
@ -25,22 +31,30 @@ impl<T> Dynamic<T> {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Maps the contents with read-only access.
|
||||
pub fn map_ref<R>(&self, map: impl FnOnce(&T) -> R) -> R {
|
||||
let state = self.state();
|
||||
map(&state.wrapped.value)
|
||||
}
|
||||
|
||||
/// Maps the contents with exclusive access. Before returning from this
|
||||
/// function, all observers will be notified that the contents have been
|
||||
/// updated.
|
||||
pub fn map_mut<R>(&self, map: impl FnOnce(&mut T) -> R) -> R {
|
||||
self.0.map_mut(map)
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&self, mut map: F)
|
||||
/// Attaches `for_each` to this value so that it is invoked each time the
|
||||
/// value's contents are updated.
|
||||
pub fn for_each<F>(&self, mut for_each: F)
|
||||
where
|
||||
F: for<'a> FnMut(&'a T) + Send + 'static,
|
||||
{
|
||||
self.0.for_each(move |gen| map(&gen.value));
|
||||
self.0.for_each(move |gen| for_each(&gen.value));
|
||||
}
|
||||
|
||||
/// Creates a new dynamic value that contains the result of invoking `map`
|
||||
/// each time this value is changed.
|
||||
pub fn map_each<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||
|
|
@ -49,14 +63,38 @@ impl<T> Dynamic<T> {
|
|||
self.0.map_each(move |gen| map(&gen.value))
|
||||
}
|
||||
|
||||
/// A helper function that invokes `with_clone` with a clone of self. This
|
||||
/// code may produce slightly more readable code.
|
||||
///
|
||||
/// ```rust
|
||||
/// let value = gooey::dynamic::Dynamic::new(1);
|
||||
///
|
||||
/// // Using with_clone
|
||||
/// value.with_clone(|value| {
|
||||
/// std::thread::spawn(move || {
|
||||
/// println!("{}", value.get());
|
||||
/// })
|
||||
/// });
|
||||
///
|
||||
/// // Using an explicit clone
|
||||
/// std::thread::spawn({
|
||||
/// let value = value.clone();
|
||||
/// move || {
|
||||
/// println!("{}", value.get());
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// println!("{}", value.get());
|
||||
/// ````
|
||||
pub fn with_clone<R>(&self, with_clone: impl FnOnce(Self) -> R) -> R {
|
||||
with_clone(self.clone())
|
||||
}
|
||||
|
||||
pub fn redraw_when_changed(&self, window: WindowHandle<WindowCommand>) {
|
||||
pub(crate) fn redraw_when_changed(&self, window: WindowHandle<WindowCommand>) {
|
||||
self.0.redraw_when_changed(window);
|
||||
}
|
||||
|
||||
/// Returns a clone of the currently contained value.
|
||||
#[must_use]
|
||||
pub fn get(&self) -> T
|
||||
where
|
||||
|
|
@ -65,19 +103,25 @@ impl<T> Dynamic<T> {
|
|||
self.0.get().value
|
||||
}
|
||||
|
||||
/// Replaces the contents with `new_value`, returning the previous contents.
|
||||
/// Before returning from this function, all observers will be notified that
|
||||
/// the contents have been updated.
|
||||
#[must_use]
|
||||
pub fn replace(&self, new_value: T) -> T {
|
||||
self.0.map_mut(|value| std::mem::replace(value, new_value))
|
||||
}
|
||||
|
||||
/// Stores `new_value` in this dynamic. Before returning from this function,
|
||||
/// all observers will be notified that the contents have been updated.
|
||||
pub fn set(&self, new_value: T) {
|
||||
let _old = self.replace(new_value);
|
||||
}
|
||||
|
||||
/// Returns a new reference-based reader for this dynamic value.
|
||||
#[must_use]
|
||||
pub fn create_ref_reader(&self) -> DynamicRefReader<T> {
|
||||
pub fn create_ref_reader(&self) -> DynamicReader<T> {
|
||||
self.state().readers += 1;
|
||||
DynamicRefReader {
|
||||
DynamicReader {
|
||||
source: self.0.clone(),
|
||||
read_generation: self.0.state().wrapped.generation,
|
||||
}
|
||||
|
|
@ -87,12 +131,22 @@ impl<T> Dynamic<T> {
|
|||
self.0.state()
|
||||
}
|
||||
|
||||
/// Returns the current generation of the value.
|
||||
#[must_use]
|
||||
pub fn generation(&self) -> usize {
|
||||
pub fn generation(&self) -> Generation {
|
||||
self.state().wrapped.generation
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Dynamic<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Dynamic<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
|
|
@ -109,7 +163,7 @@ impl<T> Drop for Dynamic<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> From<Dynamic<T>> for DynamicRefReader<T> {
|
||||
impl<T> From<Dynamic<T>> for DynamicReader<T> {
|
||||
fn from(value: Dynamic<T>) -> Self {
|
||||
value.create_ref_reader()
|
||||
}
|
||||
|
|
@ -149,7 +203,7 @@ impl<T> DynamicData<T> {
|
|||
let mut state = self.state();
|
||||
let old = {
|
||||
let state = &mut *state;
|
||||
let generation = state.wrapped.generation.wrapping_add(1);
|
||||
let generation = state.wrapped.generation.next();
|
||||
let result = map(&mut state.wrapped.value);
|
||||
state.wrapped.generation = generation;
|
||||
|
||||
|
|
@ -227,32 +281,45 @@ where
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct GenerationalValue<T> {
|
||||
struct GenerationalValue<T> {
|
||||
pub value: T,
|
||||
pub generation: usize,
|
||||
pub generation: Generation,
|
||||
}
|
||||
|
||||
/// A reader that tracks the last generation accessed through this reader.
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicRefReader<T> {
|
||||
pub struct DynamicReader<T> {
|
||||
source: Arc<DynamicData<T>>,
|
||||
read_generation: usize,
|
||||
read_generation: Generation,
|
||||
}
|
||||
|
||||
impl<T> DynamicRefReader<T> {
|
||||
impl<T> DynamicReader<T> {
|
||||
/// Maps the contents of the dynamic value and returns the result.
|
||||
///
|
||||
/// This function marks the currently stored value as being read.
|
||||
pub fn map_ref<R>(&mut self, map: impl FnOnce(&T) -> R) -> R {
|
||||
let state = self.source.state();
|
||||
self.read_generation = state.wrapped.generation;
|
||||
map(&state.wrapped.value)
|
||||
}
|
||||
|
||||
/// Returns a clone of the currently contained value.
|
||||
///
|
||||
/// This function marks the currently stored value as being read.
|
||||
#[must_use]
|
||||
pub fn get(&self) -> T
|
||||
pub fn get(&mut self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.source.get().value
|
||||
let GenerationalValue { value, generation } = self.source.get();
|
||||
self.read_generation = generation;
|
||||
value
|
||||
}
|
||||
|
||||
/// Blocks the current thread until the contained value has been updated or
|
||||
/// there are no remaining writers for the value.
|
||||
///
|
||||
/// Returns true if a newly updated value was discovered.
|
||||
pub fn block_until_updated(&mut self) -> bool {
|
||||
let mut state = self.source.state();
|
||||
loop {
|
||||
|
|
@ -269,13 +336,9 @@ impl<T> DynamicRefReader<T> {
|
|||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redraw_if_changed(&mut self, window: WindowHandle<WindowCommand>) {
|
||||
self.source.redraw_when_changed(window);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for DynamicRefReader<T> {
|
||||
impl<T> Clone for DynamicReader<T> {
|
||||
fn clone(&self) -> Self {
|
||||
self.source.state().readers += 1;
|
||||
Self {
|
||||
|
|
@ -285,7 +348,7 @@ impl<T> Clone for DynamicRefReader<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for DynamicRefReader<T> {
|
||||
impl<T> Drop for DynamicReader<T> {
|
||||
fn drop(&mut self) {
|
||||
let mut state = self.source.state();
|
||||
state.readers -= 1;
|
||||
|
|
@ -299,3 +362,103 @@ fn disconnecting_reader_from_dynamic() {
|
|||
drop(value);
|
||||
assert!(!ref_reader.block_until_updated());
|
||||
}
|
||||
|
||||
/// A tag that represents an individual revision of a [`Dynamic`] value.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
pub struct Generation(usize);
|
||||
|
||||
impl Generation {
|
||||
/// Returns the next tag.
|
||||
#[must_use]
|
||||
pub fn next(self) -> Self {
|
||||
Self(self.0.wrapping_add(1))
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that may be either constant or dynamic.
|
||||
#[derive(Debug)]
|
||||
pub enum Value<T> {
|
||||
/// A value that will not ever change externally.
|
||||
Constant(T),
|
||||
/// A value that may be updated externally.
|
||||
Dynamic(Dynamic<T>),
|
||||
}
|
||||
|
||||
impl<T> Value<T> {
|
||||
/// Returns a [`Value::Dynamic`] containing `value`.
|
||||
pub fn dynamic(value: T) -> Self {
|
||||
Self::Dynamic(Dynamic::new(value))
|
||||
}
|
||||
|
||||
/// Maps the current contents to `map` and returns the result.
|
||||
pub fn map<R>(&mut self, map: impl FnOnce(&T) -> R) -> R {
|
||||
match self {
|
||||
Value::Constant(value) => map(value),
|
||||
Value::Dynamic(dynamic) => dynamic.map_ref(map),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the current contents with exclusive access and returns the result.
|
||||
pub fn map_mut<R>(&mut self, map: impl FnOnce(&mut T) -> R) -> R {
|
||||
match self {
|
||||
Value::Constant(value) => map(value),
|
||||
Value::Dynamic(dynamic) => dynamic.map_mut(map),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a clone of the currently stored value.
|
||||
pub fn get(&mut self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.map(Clone::clone)
|
||||
}
|
||||
|
||||
/// Returns the current generation of the data stored, if the contained
|
||||
/// value is [`Dynamic`].
|
||||
pub fn generation(&self) -> Option<Generation> {
|
||||
match self {
|
||||
Value::Constant(_) => None,
|
||||
Value::Dynamic(value) => Some(value.generation()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the widget for redraw when this value is updated.
|
||||
///
|
||||
/// This function has no effect if the value is constant.
|
||||
pub fn redraw_when_changed(&self, context: &WidgetContext<'_, '_>) {
|
||||
if let Value::Dynamic(dynamic) = self {
|
||||
context.redraw_when_changed(dynamic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can be converted into a [`Value`].
|
||||
pub trait IntoValue<T> {
|
||||
/// Returns this type as a [`Value`].
|
||||
fn into_value(self) -> Value<T>;
|
||||
}
|
||||
|
||||
impl<T> IntoValue<T> for T {
|
||||
fn into_value(self) -> Value<T> {
|
||||
Value::Constant(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoValue<String> for &'a str {
|
||||
fn into_value(self) -> Value<String> {
|
||||
Value::Constant(self.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoValue<T> for Dynamic<T> {
|
||||
fn into_value(self) -> Value<T> {
|
||||
Value::Dynamic(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoValue<T> for Value<T> {
|
||||
fn into_value(self) -> Value<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
249
src/widget.rs
249
src/widget.rs
|
|
@ -1,61 +1,83 @@
|
|||
//! Types for creating reusable widgets (aka components or views).
|
||||
|
||||
use std::clone::Clone;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::ControlFlow;
|
||||
use std::ops::{ControlFlow, Deref};
|
||||
use std::panic::UnwindSafe;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
|
||||
use kludgine::app::winit::error::EventLoopError;
|
||||
use kludgine::app::winit::event::{
|
||||
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
};
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{Point, Rect, Size};
|
||||
|
||||
use crate::context::{EventContext, GraphicsContext, WidgetContext};
|
||||
use crate::dynamic::Dynamic;
|
||||
use crate::styles::{Component, Group, Styles};
|
||||
use crate::context::{EventContext, GraphicsContext};
|
||||
use crate::styles::Styles;
|
||||
use crate::tree::{Tree, WidgetId};
|
||||
use crate::widgets::Style;
|
||||
use crate::window::{RunningWindow, Window, WindowBehavior};
|
||||
use crate::{ConstraintLimit, Run};
|
||||
|
||||
/// A type that makes up a graphical user interface.
|
||||
///
|
||||
/// This type can go by many names in other UI frameworks: View, Component,
|
||||
/// Control.
|
||||
pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
||||
/// Redraw the contents of this widget.
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>);
|
||||
|
||||
/// Measure this widget and returns the ideal size based on its contents and
|
||||
/// the `available_space`.
|
||||
fn measure(
|
||||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx>;
|
||||
|
||||
/// The widget has been mounted into a parent widget.
|
||||
#[allow(unused_variables)]
|
||||
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// The widget has been removed from its parent widget.
|
||||
#[allow(unused_variables)]
|
||||
fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// Returns true if this widget should respond to mouse input at `location`.
|
||||
#[allow(unused_variables)]
|
||||
fn hit_test(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The widget is currently has a cursor hovering it at `location`.
|
||||
#[allow(unused_variables)]
|
||||
fn hover(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// The widget is no longer being hovered.
|
||||
#[allow(unused_variables)]
|
||||
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// The widget has received focus for user input.
|
||||
#[allow(unused_variables)]
|
||||
fn focus(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// The widget is no longer focused for user input.
|
||||
#[allow(unused_variables)]
|
||||
fn blur(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// The widget has become the active widget.
|
||||
#[allow(unused_variables)]
|
||||
fn activate(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// The widget is no longer active.
|
||||
#[allow(unused_variables)]
|
||||
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {}
|
||||
|
||||
/// A mouse button event has occurred at `location`. Returns whether the
|
||||
/// event has been handled or not.
|
||||
///
|
||||
/// If an event is handled, the widget will receive callbacks for
|
||||
/// [`mouse_drag`](Self::mouse_drag) and [`mouse_up`](Self::mouse_up).
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_down(
|
||||
&mut self,
|
||||
|
|
@ -64,9 +86,11 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
button: MouseButton,
|
||||
context: &mut EventContext<'_, '_>,
|
||||
) -> EventHandling {
|
||||
UNHANDLED
|
||||
IGNORED
|
||||
}
|
||||
|
||||
/// A mouse button is being held down as the cursor is moved across the
|
||||
/// widget.
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_drag(
|
||||
&mut self,
|
||||
|
|
@ -77,6 +101,7 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
) {
|
||||
}
|
||||
|
||||
/// A mouse button is no longer being pressed.
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_up(
|
||||
&mut self,
|
||||
|
|
@ -87,6 +112,8 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
) {
|
||||
}
|
||||
|
||||
/// A keyboard event has been sent to this widget. Returns whether the event
|
||||
/// has been handled or not.
|
||||
#[allow(unused_variables)]
|
||||
fn keyboard_input(
|
||||
&mut self,
|
||||
|
|
@ -95,13 +122,18 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
is_synthetic: bool,
|
||||
context: &mut EventContext<'_, '_>,
|
||||
) -> EventHandling {
|
||||
UNHANDLED
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling {
|
||||
UNHANDLED
|
||||
IGNORED
|
||||
}
|
||||
|
||||
/// An input manager event has been sent to this widget. Returns whether the
|
||||
/// event has been handled or not.
|
||||
#[allow(unused_variables)]
|
||||
fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling {
|
||||
IGNORED
|
||||
}
|
||||
|
||||
/// A mouse wheel event has been sent to this widget. Returns whether the
|
||||
/// event has been handled or not.
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_wheel(
|
||||
&mut self,
|
||||
|
|
@ -110,14 +142,17 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
phase: TouchPhase,
|
||||
context: &mut EventContext<'_, '_>,
|
||||
) -> EventHandling {
|
||||
UNHANDLED
|
||||
IGNORED
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn query_component(&self, group: Group, name: &str) -> Option<Component> {
|
||||
None
|
||||
}
|
||||
// #[allow(unused_variables)]
|
||||
// fn query_component(&self, group: Group, name: &str) -> Option<Component> {
|
||||
// None
|
||||
// }
|
||||
|
||||
/// Associates `styles` with this widget.
|
||||
///
|
||||
/// This is equivalent to `Style::new(styles, self)`.
|
||||
fn with_styles(self, styles: impl Into<Styles>) -> Style
|
||||
where
|
||||
Self: Sized,
|
||||
|
|
@ -130,15 +165,18 @@ impl<T> Run for T
|
|||
where
|
||||
T: Widget,
|
||||
{
|
||||
fn run(self) -> crate::Result<(), EventLoopError> {
|
||||
fn run(self) -> crate::Result {
|
||||
BoxedWidget::new(self).run()
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can create a widget.
|
||||
pub trait MakeWidget: Sized {
|
||||
/// Returns a new widget.
|
||||
fn make_widget(self) -> BoxedWidget;
|
||||
|
||||
fn run(self) -> Result<(), EventLoopError> {
|
||||
/// Runs the widget this type creates as an application.
|
||||
fn run(self) -> crate::Result {
|
||||
self.make_widget().run()
|
||||
}
|
||||
}
|
||||
|
|
@ -152,20 +190,29 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A type that represents whether an event has been handled or ignored.
|
||||
pub type EventHandling = ControlFlow<EventHandled, EventIgnored>;
|
||||
|
||||
/// A marker type that represents a handled event.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
|
||||
pub struct EventHandled;
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
/// A marker type that represents an ignored event.
|
||||
pub struct EventIgnored;
|
||||
|
||||
/// An [`EventHandling`] value that represents a handled event.
|
||||
pub const HANDLED: EventHandling = EventHandling::Break(EventHandled);
|
||||
pub const UNHANDLED: EventHandling = EventHandling::Continue(EventIgnored);
|
||||
|
||||
/// An [`EventHandling`] value that represents an ignored event.
|
||||
pub const IGNORED: EventHandling = EventHandling::Continue(EventIgnored);
|
||||
|
||||
/// An instance of a [`Widget`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoxedWidget(Arc<Mutex<dyn Widget>>);
|
||||
|
||||
impl BoxedWidget {
|
||||
/// Returns a new instance containing `widget`.
|
||||
pub fn new<W>(widget: W) -> Self
|
||||
where
|
||||
W: Widget,
|
||||
|
|
@ -179,7 +226,7 @@ impl BoxedWidget {
|
|||
}
|
||||
|
||||
impl Run for BoxedWidget {
|
||||
fn run(self) -> crate::Result<(), EventLoopError> {
|
||||
fn run(self) -> crate::Result {
|
||||
Window::<BoxedWidget>::new(self).run()
|
||||
}
|
||||
}
|
||||
|
|
@ -204,84 +251,9 @@ impl WindowBehavior for BoxedWidget {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value<T> {
|
||||
Constant(T),
|
||||
Dynamic(Dynamic<T>),
|
||||
}
|
||||
|
||||
impl<T> Value<T> {
|
||||
pub fn dynamic(value: T) -> Self {
|
||||
Self::Dynamic(Dynamic::new(value))
|
||||
}
|
||||
|
||||
pub fn constant(value: T) -> Self {
|
||||
Self::Constant(value)
|
||||
}
|
||||
|
||||
pub fn map<R>(&mut self, map: impl FnOnce(&T) -> R) -> R {
|
||||
match self {
|
||||
Value::Constant(value) => map(value),
|
||||
Value::Dynamic(dynamic) => dynamic.map_ref(map),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_mut<R>(&mut self, map: impl FnOnce(&mut T) -> R) -> R {
|
||||
match self {
|
||||
Value::Constant(value) => map(value),
|
||||
Value::Dynamic(dynamic) => dynamic.map_mut(map),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&mut self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.map(Clone::clone)
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> Option<usize> {
|
||||
match self {
|
||||
Value::Constant(_) => None,
|
||||
Value::Dynamic(value) => Some(value.generation()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redraw_when_changed(&self, context: &WidgetContext<'_, '_>) {
|
||||
if let Value::Dynamic(dynamic) = self {
|
||||
context.redraw_when_changed(dynamic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoValue<T> {
|
||||
fn into_value(self) -> Value<T>;
|
||||
}
|
||||
|
||||
impl<T> IntoValue<T> for T {
|
||||
fn into_value(self) -> Value<T> {
|
||||
Value::Constant(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoValue<String> for &'a str {
|
||||
fn into_value(self) -> Value<String> {
|
||||
Value::Constant(self.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoValue<T> for Dynamic<T> {
|
||||
fn into_value(self) -> Value<T> {
|
||||
Value::Dynamic(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoValue<T> for Value<T> {
|
||||
fn into_value(self) -> Value<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that can be invoked with a parameter (`T`) and returns `R`.
|
||||
///
|
||||
/// This type is used by widgets to signal various events.
|
||||
pub struct Callback<T = (), R = ()>(Box<dyn CallbackFunction<T, R>>);
|
||||
|
||||
impl<T, R> Debug for Callback<T, R> {
|
||||
|
|
@ -293,6 +265,8 @@ impl<T, R> Debug for Callback<T, R> {
|
|||
}
|
||||
|
||||
impl<T, R> Callback<T, R> {
|
||||
/// Returns a new instance that calls `function` each time the callback is
|
||||
/// invoked.
|
||||
pub fn new<F>(function: F) -> Self
|
||||
where
|
||||
F: FnMut(T) -> R + Send + UnwindSafe + 'static,
|
||||
|
|
@ -300,6 +274,7 @@ impl<T, R> Callback<T, R> {
|
|||
Self(Box::new(function))
|
||||
}
|
||||
|
||||
/// Invokes the wrapped function and returns the produced value.
|
||||
pub fn invoke(&mut self, value: T) -> R {
|
||||
self.0.invoke(value)
|
||||
}
|
||||
|
|
@ -318,6 +293,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A [`Widget`] that has been attached to a widget hierarchy.
|
||||
#[derive(Clone)]
|
||||
pub struct ManagedWidget {
|
||||
pub(crate) id: WidgetId,
|
||||
|
|
@ -343,26 +319,31 @@ impl ManagedWidget {
|
|||
self.tree.note_rendered_rect(self.id, rect);
|
||||
}
|
||||
|
||||
/// Returns the region that the widget was last rendered at.
|
||||
#[must_use]
|
||||
pub fn last_rendered_at(&self) -> Option<Rect<Px>> {
|
||||
self.tree.last_rendered_at(self.id)
|
||||
}
|
||||
|
||||
/// Returns true if this widget is the currently active widget.
|
||||
#[must_use]
|
||||
pub fn active(&self) -> bool {
|
||||
self.tree.active_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
/// Returns true if this widget is currently the hovered widget.
|
||||
#[must_use]
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.tree.hovered_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
/// Returns true if this widget is the currently focused widget.
|
||||
#[must_use]
|
||||
pub fn focused(&self) -> bool {
|
||||
self.tree.focused_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
/// Returns the parent of this widget.
|
||||
#[must_use]
|
||||
pub fn parent(&self) -> Option<ManagedWidget> {
|
||||
self.tree.parent(self.id).map(|id| self.tree.widget(id))
|
||||
|
|
@ -384,3 +365,75 @@ impl PartialEq<BoxedWidget> for ManagedWidget {
|
|||
&self.widget == other
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of [`Widget`]s.
|
||||
#[derive(Debug, Default)]
|
||||
#[must_use]
|
||||
pub struct Widgets {
|
||||
ordered: Vec<BoxedWidget>,
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
/// Returns an empty list.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ordered: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list with enough capacity to hold `capacity` widgets without
|
||||
/// reallocation.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
ordered: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes `widget` into the list.
|
||||
pub fn push<W>(&mut self, widget: W)
|
||||
where
|
||||
W: MakeWidget,
|
||||
{
|
||||
self.ordered.push(widget.make_widget());
|
||||
}
|
||||
|
||||
/// Adds `widget` to self and returns the updated list.
|
||||
pub fn with_widget<W>(mut self, widget: W) -> Self
|
||||
where
|
||||
W: MakeWidget,
|
||||
{
|
||||
self.push(widget);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the number of widgets in this list.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.ordered.len()
|
||||
}
|
||||
|
||||
/// Returns true if there are no widgets in this list.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.ordered.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> FromIterator<W> for Widgets
|
||||
where
|
||||
W: MakeWidget,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = W>>(iter: T) -> Self {
|
||||
Self {
|
||||
ordered: iter.into_iter().map(MakeWidget::make_widget).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Widgets {
|
||||
type Target = [BoxedWidget];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ordered
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//! Built-in [`Widget`](crate::widget::Widget) implementations.
|
||||
|
||||
pub mod array;
|
||||
mod button;
|
||||
mod canvas;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,35 @@
|
|||
//! A widget that combines an array of [`Widgets`] into one.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use alot::{LotId, OrderedLots};
|
||||
use kludgine::figures::units::UPx;
|
||||
use kludgine::figures::{Point, Rect, Size};
|
||||
|
||||
use crate::children::Children;
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext};
|
||||
use crate::widget::{IntoValue, ManagedWidget, Value, Widget};
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
use crate::widget::{ManagedWidget, Widget, Widgets};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A widget that displays a collection of [`Widgets`] in a
|
||||
/// [direction](ArrayDirection).
|
||||
#[derive(Debug)]
|
||||
pub struct Array {
|
||||
/// The direction to display the children using.
|
||||
pub direction: Value<ArrayDirection>,
|
||||
pub children: Value<Children>,
|
||||
/// The children widgets that belong to this array.
|
||||
pub children: Value<Widgets>,
|
||||
layout: Layout,
|
||||
layout_generation: Option<usize>,
|
||||
layout_generation: Option<Generation>,
|
||||
// TODO Refactor synced_children into its own type.
|
||||
synced_children: Vec<ManagedWidget>,
|
||||
}
|
||||
|
||||
impl Array {
|
||||
/// Returns a new widget with the given direction and widgets.
|
||||
pub fn new(
|
||||
direction: impl IntoValue<ArrayDirection>,
|
||||
children: impl IntoValue<Children>,
|
||||
widgets: impl IntoValue<Widgets>,
|
||||
) -> Self {
|
||||
let mut direction = direction.into_value();
|
||||
|
||||
|
|
@ -29,25 +37,27 @@ impl Array {
|
|||
|
||||
Self {
|
||||
direction,
|
||||
children: children.into_value(),
|
||||
children: widgets.into_value(),
|
||||
layout: Layout::new(initial_direction),
|
||||
layout_generation: None,
|
||||
synced_children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn columns(children: impl IntoValue<Children>) -> Self {
|
||||
Self::new(ArrayDirection::columns(), children)
|
||||
/// Returns a new instance that displays `widgets` in a series of columns.
|
||||
pub fn columns(widgets: impl IntoValue<Widgets>) -> Self {
|
||||
Self::new(ArrayDirection::columns(), widgets)
|
||||
}
|
||||
|
||||
pub fn rows(children: impl IntoValue<Children>) -> Self {
|
||||
Self::new(ArrayDirection::rows(), children)
|
||||
/// Returns a new instance that displays `widgets` in a series of rows.
|
||||
pub fn rows(widgets: impl IntoValue<Widgets>) -> Self {
|
||||
Self::new(ArrayDirection::rows(), widgets)
|
||||
}
|
||||
|
||||
fn synchronize_children(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
let current_generation = self.children.generation();
|
||||
if current_generation.map_or_else(
|
||||
|| self.children.map(Children::len) != self.layout.children.len(),
|
||||
|| self.children.map(Widgets::len) != self.layout.children.len(),
|
||||
|gen| Some(gen) != self.layout_generation,
|
||||
) {
|
||||
self.layout_generation = self.children.generation();
|
||||
|
|
@ -134,60 +144,99 @@ impl Widget for Array {
|
|||
}
|
||||
}
|
||||
|
||||
/// The direction of an [`Array`] widget.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ArrayDirection {
|
||||
/// The orientation of the widgets.
|
||||
pub orientation: ArrayOrientation,
|
||||
/// If true, the widgets will be laid out in reverse order.
|
||||
pub reverse: bool,
|
||||
}
|
||||
|
||||
/// The orientation (Row/Column) of an [`Array`] widget.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
|
||||
pub enum ArrayDirection {
|
||||
Row { reverse: bool },
|
||||
Column { reverse: bool },
|
||||
pub enum ArrayOrientation {
|
||||
/// The child widgets should be displayed as rows.
|
||||
Row,
|
||||
/// The child widgets should be displayed as columns.
|
||||
Column,
|
||||
}
|
||||
|
||||
impl ArrayDirection {
|
||||
/// Display child widgets as columns.
|
||||
#[must_use]
|
||||
pub const fn columns() -> Self {
|
||||
Self::Column { reverse: false }
|
||||
Self {
|
||||
orientation: ArrayOrientation::Column,
|
||||
reverse: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Display child widgets as columns in reverse order.
|
||||
#[must_use]
|
||||
pub const fn columns_rev() -> Self {
|
||||
Self::Column { reverse: true }
|
||||
Self {
|
||||
orientation: ArrayOrientation::Column,
|
||||
reverse: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Display child widgets as rows.
|
||||
#[must_use]
|
||||
pub const fn rows() -> Self {
|
||||
Self::Row { reverse: false }
|
||||
Self {
|
||||
orientation: ArrayOrientation::Row,
|
||||
reverse: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Display child widgets as rows in reverse order.
|
||||
#[must_use]
|
||||
pub const fn rows_rev() -> Self {
|
||||
Self::Row { reverse: true }
|
||||
}
|
||||
|
||||
pub fn split_size<U>(&self, s: Size<U>) -> (U, U) {
|
||||
match self {
|
||||
Self::Row { .. } => (s.height, s.width),
|
||||
Self::Column { .. } => (s.width, s.height),
|
||||
Self {
|
||||
orientation: ArrayOrientation::Row,
|
||||
reverse: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_size<U>(&self, measured: U, other: U) -> Size<U> {
|
||||
match self {
|
||||
Self::Row { .. } => Size::new(other, measured),
|
||||
Self::Column { .. } => Size::new(measured, other),
|
||||
/// Splits a size into its measured and other parts.
|
||||
pub(crate) fn split_size<U>(self, s: Size<U>) -> (U, U) {
|
||||
match self.orientation {
|
||||
ArrayOrientation::Row => (s.height, s.width),
|
||||
ArrayOrientation::Column => (s.width, s.height),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_point<U>(&self, measured: U, other: U) -> Point<U> {
|
||||
match self {
|
||||
Self::Row { .. } => Point::new(other, measured),
|
||||
Self::Column { .. } => Point::new(measured, other),
|
||||
/// Combines split values into a [`Size`].
|
||||
pub(crate) fn make_size<U>(self, measured: U, other: U) -> Size<U> {
|
||||
match self.orientation {
|
||||
ArrayOrientation::Row => Size::new(other, measured),
|
||||
ArrayOrientation::Column => Size::new(measured, other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines split values into a [`Point`].
|
||||
pub(crate) fn make_point<U>(self, measured: U, other: U) -> Point<U> {
|
||||
match self.orientation {
|
||||
ArrayOrientation::Row => Point::new(other, measured),
|
||||
ArrayOrientation::Column => Point::new(measured, other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The strategy to use when laying a widget out inside of an [`Array`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ArrayDimension {
|
||||
/// Attempt to lay out the widget based on its contents.
|
||||
FitContent,
|
||||
Fractional { weight: u8 },
|
||||
/// Use a fractional amount of the available space.
|
||||
Fractional {
|
||||
/// The weight to apply to this widget when dividing multiple widgets
|
||||
/// fractionally.
|
||||
weight: u8,
|
||||
},
|
||||
/// Use an exact measurement for this widget's size.
|
||||
Exact(UPx),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,19 +11,23 @@ use kludgine::Color;
|
|||
|
||||
use crate::context::{EventContext, GraphicsContext};
|
||||
use crate::names::Name;
|
||||
use crate::styles::{
|
||||
ComponentDefinition, ComponentGroup, ComponentName, HighlightColor, NamedComponent, TextColor,
|
||||
};
|
||||
use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
||||
use crate::styles::components::{HighlightColor, TextColor};
|
||||
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
|
||||
use crate::value::{IntoValue, Value};
|
||||
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
|
||||
|
||||
/// A clickable button.
|
||||
#[derive(Debug)]
|
||||
pub struct Button {
|
||||
/// The label to display on the button.
|
||||
pub label: Value<String>,
|
||||
/// The callback that is invoked when the button is clicked.
|
||||
pub on_click: Option<Callback<()>>,
|
||||
buttons_pressed: usize,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
/// Returns a new button with the provided label.
|
||||
pub fn new(label: impl IntoValue<String>) -> Self {
|
||||
Self {
|
||||
label: label.into_value(),
|
||||
|
|
@ -32,6 +36,9 @@ impl Button {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the `on_click` callback and returns self.
|
||||
///
|
||||
/// This callback will be invoked each time the button is clicked.
|
||||
#[must_use]
|
||||
pub fn on_click<F>(mut self, callback: F) -> Self
|
||||
where
|
||||
|
|
@ -76,7 +83,7 @@ impl Widget for Button {
|
|||
.draw_shape(&background, Point::default(), None, None);
|
||||
|
||||
if context.focused() {
|
||||
context.draw_focus_ring(&styles);
|
||||
context.draw_focus_ring_using(&styles);
|
||||
}
|
||||
|
||||
let width = context.graphics.size().width;
|
||||
|
|
@ -198,7 +205,7 @@ impl Widget for Button {
|
|||
}
|
||||
HANDLED
|
||||
} else {
|
||||
UNHANDLED
|
||||
IGNORED
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
use std::fmt::Debug;
|
||||
use std::panic::UnwindSafe;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use kludgine::figures::units::UPx;
|
||||
use kludgine::figures::Size;
|
||||
|
||||
use crate::context::GraphicsContext;
|
||||
use crate::value::Dynamic;
|
||||
use crate::widget::Widget;
|
||||
use crate::Tick;
|
||||
|
||||
/// A 2d drawable surface.
|
||||
#[must_use]
|
||||
pub struct Canvas {
|
||||
render: Box<dyn RenderFunction>,
|
||||
target_frame_duration: Option<Duration>,
|
||||
last_frame_time: Option<Instant>,
|
||||
tick: Option<Tick>,
|
||||
redraw: Dynamic<()>,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
/// Returns a new canvas that draws its contents by invoking `render`.
|
||||
pub fn new<F>(render: F) -> Self
|
||||
where
|
||||
F: for<'clip, 'gfx, 'pass, 'context, 'window> FnMut(
|
||||
|
|
@ -26,30 +29,24 @@ impl Canvas {
|
|||
{
|
||||
Self {
|
||||
render: Box::new(render),
|
||||
target_frame_duration: None,
|
||||
last_frame_time: None,
|
||||
tick: None,
|
||||
redraw: Dynamic::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn target_fps(mut self, fps: u16) -> Self {
|
||||
const ONE_SECOND_NS: u64 = 1_000_000_000;
|
||||
let frame_duration = ONE_SECOND_NS / u64::from(fps);
|
||||
self.target_frame_duration = Some(Duration::from_nanos(frame_duration));
|
||||
/// Associates a [`Tick`] with this widget and returns self.
|
||||
pub fn tick(mut self, tick: Tick) -> Self {
|
||||
self.tick = Some(tick);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Canvas {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
context.redraw_when_changed(&self.redraw);
|
||||
self.render.render(context);
|
||||
|
||||
if let Some(target_frame_duration) = self.target_frame_duration {
|
||||
let now = Instant::now();
|
||||
let max_target = now + target_frame_duration;
|
||||
let next_frame_target = self.last_frame_time.map_or(max_target, |last_frame_time| {
|
||||
max_target.max(last_frame_time + target_frame_duration)
|
||||
});
|
||||
context.redraw_at(next_frame_target);
|
||||
if let Some(tick) = &self.tick {
|
||||
tick.rendered(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,24 +14,30 @@ use kludgine::text::TextOrigin;
|
|||
use kludgine::{Color, Kludgine};
|
||||
|
||||
use crate::context::{EventContext, WidgetContext};
|
||||
use crate::styles::{HighlightColor, LineHeight, Styles, TextColor, TextSize};
|
||||
use crate::styles::components::{HighlightColor, LineHeight, TextColor, TextSize};
|
||||
use crate::styles::Styles;
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::widget::{EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
use crate::widget::{EventHandling, Widget, HANDLED, IGNORED};
|
||||
|
||||
const CURSOR_BLINK_DURATION: Duration = Duration::from_millis(500);
|
||||
|
||||
/// A text input widget.
|
||||
#[must_use]
|
||||
pub struct Input {
|
||||
/// The value of this widget.
|
||||
pub text: Value<String>,
|
||||
editor: Option<LiveEditor>,
|
||||
cursor_state: CursorState,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
/// Returns an empty widget.
|
||||
pub fn empty() -> Self {
|
||||
Self::new(String::new())
|
||||
}
|
||||
|
||||
/// Returns a new widget containing `initial_text`.
|
||||
pub fn new(initial_text: impl IntoValue<String>) -> Self {
|
||||
Self {
|
||||
text: initial_text.into_value(),
|
||||
|
|
@ -149,7 +155,7 @@ impl Widget for Input {
|
|||
buffer.shape_until_scroll(context.graphics.font_system());
|
||||
|
||||
if context.focused() {
|
||||
context.draw_focus_ring(&styles);
|
||||
context.draw_focus_ring_using(&styles);
|
||||
context.set_ime_allowed(true);
|
||||
let line_height = Px::from_float(buffer.metrics().line_height);
|
||||
if let Some(selection) = selection {
|
||||
|
|
@ -310,7 +316,7 @@ impl Widget for Input {
|
|||
context: &mut EventContext<'_, '_>,
|
||||
) -> EventHandling {
|
||||
if !input.state.is_pressed() {
|
||||
return UNHANDLED;
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
|
|
@ -362,7 +368,7 @@ impl Widget for Input {
|
|||
editor.insert_string(&text, None);
|
||||
HANDLED
|
||||
}
|
||||
(_, _) => UNHANDLED,
|
||||
(_, _) => IGNORED,
|
||||
};
|
||||
|
||||
if handled.is_break() {
|
||||
|
|
@ -400,7 +406,7 @@ impl Widget for Input {
|
|||
|
||||
struct LiveEditor {
|
||||
editor: Editor,
|
||||
generation: Option<usize>,
|
||||
generation: Option<Generation>,
|
||||
}
|
||||
|
||||
fn cursor_glyph(buffer: &Buffer, cursor: &Cursor) -> Result<(Point<Px>, Px), NotVisible> {
|
||||
|
|
|
|||
|
|
@ -3,30 +3,34 @@ use kludgine::figures::{Point, Size};
|
|||
use kludgine::text::{Text, TextOrigin};
|
||||
|
||||
use crate::context::GraphicsContext;
|
||||
use crate::styles::TextColor;
|
||||
use crate::widget::{IntoValue, Value, Widget};
|
||||
use crate::styles::components::TextColor;
|
||||
use crate::value::{IntoValue, Value};
|
||||
use crate::widget::Widget;
|
||||
|
||||
/// A read-only text widget.
|
||||
#[derive(Debug)]
|
||||
pub struct Label {
|
||||
pub contents: Value<String>,
|
||||
/// The contents of the label.
|
||||
pub text: Value<String>,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
pub fn new(contents: impl IntoValue<String>) -> Self {
|
||||
/// Returns a new label that displays `text`.
|
||||
pub fn new(text: impl IntoValue<String>) -> Self {
|
||||
Self {
|
||||
contents: contents.into_value(),
|
||||
text: text.into_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Label {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
self.contents.redraw_when_changed(context);
|
||||
self.text.redraw_when_changed(context);
|
||||
|
||||
let center = Point::from(context.graphics.size()) / 2;
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let width = context.graphics.size().width;
|
||||
self.contents.map(|contents| {
|
||||
self.text.map(|contents| {
|
||||
context.graphics.draw_text(
|
||||
Text::new(contents, styles.get_or_default(&TextColor))
|
||||
.origin(TextOrigin::Center)
|
||||
|
|
@ -44,7 +48,7 @@ impl Widget for Label {
|
|||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||
self.contents.map(|contents| {
|
||||
self.text.map(|contents| {
|
||||
context
|
||||
.graphics
|
||||
.measure_text(Text::from(contents).wrap_at(width))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::styles::Styles;
|
|||
use crate::widget::{BoxedWidget, ManagedWidget, Widget};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A widget that applies a set of [`Styles`] to all contained widgets.
|
||||
#[derive(Debug)]
|
||||
pub struct Style {
|
||||
styles: Styles,
|
||||
|
|
@ -14,6 +15,8 @@ pub struct Style {
|
|||
}
|
||||
|
||||
impl Style {
|
||||
/// Returns a new widget that applies `styles` to `child` and any children
|
||||
/// it may have.
|
||||
pub fn new(styles: impl Into<Styles>, child: impl Widget) -> Self {
|
||||
Self {
|
||||
styles: styles.into(),
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
use std::fmt::Debug;
|
||||
use std::panic::UnwindSafe;
|
||||
|
||||
use kludgine::figures::utils::lossy_f64_to_f32;
|
||||
|
||||
use crate::context::{EventContext, GraphicsContext};
|
||||
use crate::dynamic::Dynamic;
|
||||
use crate::kludgine::app::winit::event::{DeviceId, KeyEvent, MouseScrollDelta, TouchPhase};
|
||||
use crate::kludgine::app::winit::keyboard::Key;
|
||||
use crate::kludgine::figures::units::UPx;
|
||||
use crate::kludgine::figures::Size;
|
||||
use crate::kludgine::tilemap;
|
||||
use crate::kludgine::tilemap::TileMapFocus;
|
||||
use crate::tick::Tick;
|
||||
use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
||||
use crate::value::{Dynamic, IntoValue, Value};
|
||||
use crate::widget::{EventHandling, Widget, HANDLED, IGNORED};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A layered tile-based 2d game surface.
|
||||
#[derive(Debug)]
|
||||
#[must_use]
|
||||
pub struct TileMap<Layers> {
|
||||
layers: Value<Layers>,
|
||||
focus: Value<TileMapFocus>,
|
||||
key: Option<Callback<Key, EventHandling>>,
|
||||
zoom: f32,
|
||||
tick: Option<Tick>,
|
||||
}
|
||||
|
|
@ -31,32 +29,30 @@ impl<Layers> TileMap<Layers> {
|
|||
layers,
|
||||
focus: Value::Constant(TileMapFocus::default()),
|
||||
zoom: 1.,
|
||||
key: None,
|
||||
tick: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new tilemap that contains dynamic layers.
|
||||
pub fn dynamic(layers: Dynamic<Layers>) -> Self {
|
||||
Self::construct(Value::Dynamic(layers))
|
||||
}
|
||||
|
||||
/// Returns a new tilemap that renders `layers`.
|
||||
pub fn new(layers: Layers) -> Self {
|
||||
Self::construct(Value::Constant(layers))
|
||||
}
|
||||
|
||||
/// Sets the camera's focus and returns self.
|
||||
///
|
||||
/// The tilemap will ensure that `focus` is centered.
|
||||
// TODO how do we allow the camera to "lag" for juice effects?
|
||||
pub fn focus_on(mut self, focus: impl IntoValue<TileMapFocus>) -> Self {
|
||||
self.focus = focus.into_value();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_key<F>(mut self, key: F) -> Self
|
||||
where
|
||||
F: FnMut(Key) -> EventHandling + Send + UnwindSafe + 'static,
|
||||
{
|
||||
self.key = Some(Callback::new(key));
|
||||
self
|
||||
}
|
||||
|
||||
/// Associates a [`Tick`] with this widget and returns self.
|
||||
pub fn tick(mut self, tick: Tick) -> Self {
|
||||
self.tick = Some(tick);
|
||||
self
|
||||
|
|
@ -68,15 +64,15 @@ where
|
|||
Layers: tilemap::Layers,
|
||||
{
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
self.focus.redraw_when_changed(context);
|
||||
self.layers.redraw_when_changed(context);
|
||||
|
||||
let focus = self.focus.get();
|
||||
self.layers
|
||||
.map(|layers| tilemap::draw(layers, focus, self.zoom, &mut context.graphics));
|
||||
|
||||
if let Some(tick) = &self.tick {
|
||||
tick.rendered();
|
||||
tick.rendered(context);
|
||||
} else {
|
||||
self.focus.redraw_when_changed(context);
|
||||
self.layers.redraw_when_changed(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,13 +112,7 @@ where
|
|||
if let Some(tick) = &self.tick {
|
||||
tick.key_input(&input)?;
|
||||
}
|
||||
if !input.state.is_pressed() {
|
||||
return UNHANDLED;
|
||||
}
|
||||
if let Some(on_key) = &mut self.key {
|
||||
on_key.invoke(input.logical_key.clone())?;
|
||||
}
|
||||
|
||||
UNHANDLED
|
||||
IGNORED
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
//! Types for displaying a [`Widget`](crate::widget::Widget) inside of a desktop
|
||||
//! window.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
|
||||
use kludgine::app::winit::dpi::PhysicalPosition;
|
||||
use kludgine::app::winit::error::EventLoopError;
|
||||
use kludgine::app::winit::event::{
|
||||
DeviceId, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
};
|
||||
|
|
@ -18,19 +20,24 @@ use crate::context::{EventContext, Exclusive, GraphicsContext, WidgetContext};
|
|||
use crate::graphics::Graphics;
|
||||
use crate::tree::Tree;
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::widget::{BoxedWidget, EventHandling, ManagedWidget, Widget, HANDLED, UNHANDLED};
|
||||
use crate::widget::{BoxedWidget, EventHandling, ManagedWidget, Widget, HANDLED, IGNORED};
|
||||
use crate::window::sealed::WindowCommand;
|
||||
use crate::Run;
|
||||
|
||||
/// A currently running Gooey window.
|
||||
pub type RunningWindow<'window> = kludgine::app::Window<'window, WindowCommand>;
|
||||
|
||||
/// The attributes of a Gooey window.
|
||||
pub type WindowAttributes = kludgine::app::WindowAttributes<WindowCommand>;
|
||||
|
||||
/// A Gooey window that is not yet running.
|
||||
#[must_use]
|
||||
pub struct Window<Behavior>
|
||||
where
|
||||
Behavior: WindowBehavior,
|
||||
{
|
||||
context: Behavior::Context,
|
||||
/// The attributes of this window.
|
||||
pub attributes: WindowAttributes,
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +53,7 @@ where
|
|||
}
|
||||
|
||||
impl Window<BoxedWidget> {
|
||||
/// Returns a new instance using `widget` as its contents.
|
||||
pub fn for_widget<W>(widget: W) -> Self
|
||||
where
|
||||
W: Widget,
|
||||
|
|
@ -58,6 +66,8 @@ impl<Behavior> Window<Behavior>
|
|||
where
|
||||
Behavior: WindowBehavior,
|
||||
{
|
||||
/// Returns a new instance using `context` to initialize the window upon
|
||||
/// opening.
|
||||
pub fn new(context: Behavior::Context) -> Self {
|
||||
Self {
|
||||
attributes: WindowAttributes {
|
||||
|
|
@ -73,36 +83,45 @@ impl<Behavior> Run for Window<Behavior>
|
|||
where
|
||||
Behavior: WindowBehavior,
|
||||
{
|
||||
fn run(self) -> crate::Result<(), EventLoopError> {
|
||||
fn run(self) -> crate::Result {
|
||||
GooeyWindow::<Behavior>::run_with(AssertUnwindSafe((
|
||||
self.context,
|
||||
RefCell::new(WindowSettings {
|
||||
RefCell::new(sealed::WindowSettings {
|
||||
attributes: Some(self.attributes),
|
||||
}),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The behavior of a Gooey window.
|
||||
pub trait WindowBehavior: Sized + 'static {
|
||||
/// The type that is provided when initializing this window.
|
||||
type Context: UnwindSafe + Send + 'static;
|
||||
|
||||
/// Return a new instance of this behavior using `context`.
|
||||
fn initialize(window: &mut RunningWindow<'_>, context: Self::Context) -> Self;
|
||||
|
||||
/// Create the window's root widget. This function is only invoked once.
|
||||
fn make_root(&mut self) -> BoxedWidget;
|
||||
|
||||
/// The window has been requested to close. If this function returns true,
|
||||
/// the window will be closed. Returning false prevents the window from
|
||||
/// closing.
|
||||
#[allow(unused_variables)]
|
||||
fn close_requested(&self, window: &mut RunningWindow<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run() -> Result<(), EventLoopError>
|
||||
/// Runs this behavior as an application.
|
||||
fn run() -> crate::Result
|
||||
where
|
||||
Self::Context: Default,
|
||||
{
|
||||
Self::run_with(<Self::Context>::default())
|
||||
}
|
||||
|
||||
fn run_with(context: Self::Context) -> Result<(), EventLoopError> {
|
||||
/// Runs this behavior as an application, initialized with `context`.
|
||||
fn run_with(context: Self::Context) -> crate::Result {
|
||||
Window::<Self>::new(context).run()
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +149,7 @@ impl<T> kludgine::app::WindowBehavior<WindowCommand> for GooeyWindow<T>
|
|||
where
|
||||
T: WindowBehavior,
|
||||
{
|
||||
type Context = AssertUnwindSafe<(T::Context, RefCell<WindowSettings>)>;
|
||||
type Context = AssertUnwindSafe<(T::Context, RefCell<sealed::WindowSettings>)>;
|
||||
|
||||
fn initialize(
|
||||
mut window: RunningWindow<'_>,
|
||||
|
|
@ -415,16 +434,12 @@ fn recursively_handle_event(
|
|||
) -> Option<ManagedWidget> {
|
||||
match each_widget(context) {
|
||||
HANDLED => Some(context.widget().clone()),
|
||||
UNHANDLED => context.parent().and_then(|parent| {
|
||||
IGNORED => context.parent().and_then(|parent| {
|
||||
recursively_handle_event(&mut context.for_other(&parent), each_widget)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowSettings {
|
||||
attributes: Option<WindowAttributes>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MouseState {
|
||||
location: Option<Point<Px>>,
|
||||
|
|
@ -433,6 +448,12 @@ struct MouseState {
|
|||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
pub struct WindowSettings {
|
||||
pub attributes: Option<WindowAttributes>,
|
||||
}
|
||||
|
||||
pub enum WindowCommand {
|
||||
Redraw,
|
||||
// RequestClose,
|
||||
|
|
|
|||
Loading…
Reference in a new issue