Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement timers in winit #792

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions masonry/examples/animation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2019 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0

//! This is a very small example of how to setup a masonry application.
//! It does the almost bare minimum while still being useful.

// On Windows platform, don't show a console when opening the app.
//#![windows_subsystem = "windows"]

use masonry::dpi::LogicalSize;
use masonry::text::StyleProperty;
use masonry::widget::{Button, Flex, Label, ProgressBar, RootWidget};
use masonry::{Action, AppDriver, DriverCtx, FontWeight, WidgetId};
use winit::window::Window;

const VERTICAL_WIDGET_SPACING: f64 = 20.0;

struct Driver;

impl AppDriver for Driver {
fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
match action {
Action::ButtonPressed(_) => {
println!("Hello");
}
action => {
eprintln!("Unexpected action {action:?}");
}
}
}
}

fn main() {
let progress_bar = ProgressBar::new(None).animate(true);

let window_size = LogicalSize::new(400.0, 400.0);
let window_attributes = Window::default_attributes()
.with_title("Hello World!")
.with_resizable(true)
.with_min_inner_size(window_size);

masonry::event_loop_runner::run(
masonry::event_loop_runner::EventLoop::with_user_event(),
window_attributes,
RootWidget::new(progress_bar),
Driver,
)
.unwrap();
}
16 changes: 10 additions & 6 deletions masonry/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

//! The context types that are passed into various widget methods.

use std::time::Duration;
use std::time::{Duration, Instant};

use accesskit::TreeUpdate;
use dpi::LogicalPosition;
Expand All @@ -19,6 +19,8 @@ use crate::passes::layout::run_layout_on;
use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState};
use crate::text::BrushIndex;
use crate::theme::get_debug_color;
use crate::timers::Timer;
pub use crate::timers::TimerId;
use crate::widget::{WidgetMut, WidgetRef, WidgetState};
use crate::{AllowRawMut, BoxConstraints, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod};

Expand Down Expand Up @@ -713,8 +715,13 @@ impl_context_method!(
///
/// The return value is a token, which can be used to associate the
/// request with the event.
pub fn request_timer(&mut self, _deadline: Duration) -> TimerToken {
todo!("request_timer");
pub fn request_timer(&mut self, deadline: Duration) -> TimerId {
let deadline = Instant::now() + deadline;
let timer = Timer::new(self.widget_id(), deadline);
let id = timer.id;
self.global_state
.emit_signal(RenderRootSignal::TimerRequested(timer));
id
}

/// Mark child widget as stashed.
Expand All @@ -737,9 +744,6 @@ impl_context_method!(
}
);

// FIXME - Remove
pub struct TimerToken;

impl EventCtx<'_> {
// TODO - clearly document all semantics of pointer capture when they've been decided on
// TODO - Figure out cases where widget should be notified of pointer capture
Expand Down
8 changes: 8 additions & 0 deletions masonry/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
//! Events.

use std::path::PathBuf;
use std::time::Instant;

use winit::event::{Force, Ime, KeyEvent, Modifiers};
use winit::keyboard::ModifiersState;

use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::kurbo::Rect;
use crate::timers::TimerId;

// TODO - Occluded(bool) event
// TODO - winit ActivationTokenDone thing
Expand Down Expand Up @@ -213,6 +215,12 @@ pub struct AccessEvent {
pub data: Option<accesskit::ActionData>,
}

#[derive(Debug, Clone, Copy)]
pub struct TimerEvent {
pub id: TimerId,
pub deadline: Instant,
}

#[derive(Debug, Clone)]
pub struct PointerState {
// TODO
Expand Down
56 changes: 44 additions & 12 deletions masonry/src/event_loop_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::Instant;

use accesskit_winit::Adapter;
use tracing::{debug, info_span, warn};
Expand All @@ -17,13 +18,14 @@ use winit::event::{
DeviceEvent as WinitDeviceEvent, DeviceId, MouseButton as WinitMouseButton,
WindowEvent as WinitWindowEvent,
};
use winit::event_loop::ActiveEventLoop;
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::window::{Window, WindowAttributes, WindowId};

use crate::app_driver::{AppDriver, DriverCtx};
use crate::dpi::LogicalPosition;
use crate::event::{PointerButton, PointerState, WindowEvent};
use crate::render_root::{self, RenderRoot, WindowSizePolicy};
use crate::timers::TimerQueue;
use crate::{PointerEvent, TextEvent, Widget, WidgetId};

#[derive(Debug)]
Expand Down Expand Up @@ -81,6 +83,7 @@ pub struct MasonryState<'a> {
proxy: EventLoopProxy,
#[cfg(feature = "tracy")]
frame: Option<tracing_tracy::client::Frame>,
timers: TimerQueue,

// Per-Window state
// In future, this will support multiple windows
Expand Down Expand Up @@ -159,6 +162,14 @@ impl ApplicationHandler<MasonryUserEvent> for MainState<'_> {
self.masonry_state.handle_suspended(event_loop);
}

fn new_events(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
self.masonry_state.handle_new_events(event_loop, cause);
}

fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
Expand Down Expand Up @@ -200,14 +211,6 @@ impl ApplicationHandler<MasonryUserEvent> for MainState<'_> {
self.masonry_state.handle_about_to_wait(event_loop);
}

fn new_events(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
self.masonry_state.handle_new_events(event_loop, cause);
}

fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
self.masonry_state.handle_exiting(event_loop);
}
Expand Down Expand Up @@ -244,6 +247,7 @@ impl MasonryState<'_> {
frame: None,
pointer_state: PointerState::empty(),
proxy: event_loop.create_proxy(),
timers: TimerQueue::new(),

window: WindowState::Uninitialized(window),
background_color,
Expand Down Expand Up @@ -658,10 +662,35 @@ impl MasonryState<'_> {
self.handle_signals(event_loop, app_driver);
}

// --- MARK: EMPTY WINIT HANDLERS ---
pub fn handle_about_to_wait(&mut self, _: &ActiveEventLoop) {}
// --- MARK: TIMERS ---
pub fn handle_new_events(&mut self, _: &ActiveEventLoop, _: winit::event::StartCause) {
// check if timers have elapsed and set event loop to wake at next timer deadline
let now = Instant::now();
loop {
let Some(next_timer) = self.timers.peek() else {
break;
};
if next_timer.deadline > now {
break;
}
// timer has elapsed - remove from heap and handle
let Some(elapsed_timer) = self.timers.pop() else {
debug_panic!("should be unreachable: peek was Some");
break;
};
self.render_root.handle_elapsed_timer(elapsed_timer);
}
}

pub fn handle_new_events(&mut self, _: &ActiveEventLoop, _: winit::event::StartCause) {}
pub fn handle_about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if let Some(next_timer) = self.timers.peek() {
event_loop.set_control_flow(ControlFlow::WaitUntil(next_timer.deadline));
} else {
event_loop.set_control_flow(ControlFlow::Wait);
}
}

// --- MARK: EMPTY WINIT HANDLERS ---

pub fn handle_exiting(&mut self, _: &ActiveEventLoop) {}

Expand All @@ -686,6 +715,9 @@ impl MasonryState<'_> {
app_driver.on_action(&mut driver_ctx, widget_id, action);
});
}
render_root::RenderRootSignal::TimerRequested(timer) => {
self.timers.push(timer);
}
render_root::RenderRootSignal::StartIme => {
window.set_ime_allowed(true);
}
Expand Down
1 change: 1 addition & 0 deletions masonry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ mod event;
mod paint_scene_helpers;
mod passes;
mod render_root;
mod timers;
mod tracing_backend;

pub mod event_loop_runner;
Expand Down
22 changes: 22 additions & 0 deletions masonry/src/passes/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use tracing::{debug, info_span, trace};
use winit::event::ElementState;
use winit::keyboard::{KeyCode, PhysicalKey};

use crate::event::TimerEvent;
use crate::passes::{enter_span, merge_state_up};
use crate::render_root::RenderRoot;
use crate::timers::Timer;
use crate::{AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, Widget, WidgetId};

// --- MARK: HELPERS ---
Expand Down Expand Up @@ -135,6 +137,26 @@ pub(crate) fn run_on_pointer_event_pass(root: &mut RenderRoot, event: &PointerEv
handled
}

pub(crate) fn run_on_elapsed_timer_pass(root: &mut RenderRoot, timer: &Timer) -> Handled {
let event = TimerEvent {
deadline: timer.deadline,
id: timer.id,
};
let handled = run_event_pass(
root,
Some(timer.widget_id),
&event,
false,
|widget, ctx, event| {
widget.on_timer_expired(ctx, event);
// don't traverse for this event
ctx.set_handled();
},
true,
);
handled
}

// TODO https://github.com/linebender/xilem/issues/376 - Some implicit invariants:
// - If a Widget gets a keyboard event or an ImeStateChange, then
// focus is on it, its child or its parent.
Expand Down
13 changes: 12 additions & 1 deletion masonry/src/render_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use crate::passes::accessibility::run_accessibility_pass;
use crate::passes::anim::run_update_anim_pass;
use crate::passes::compose::run_compose_pass;
use crate::passes::event::{
run_on_access_event_pass, run_on_pointer_event_pass, run_on_text_event_pass,
run_on_access_event_pass, run_on_elapsed_timer_pass, run_on_pointer_event_pass,
run_on_text_event_pass,
};
use crate::passes::layout::run_layout_pass;
use crate::passes::mutate::{mutate_widget, run_mutate_pass};
Expand All @@ -36,6 +37,7 @@ use crate::passes::update::{
};
use crate::passes::{recurse_on_children, PassTracing};
use crate::text::BrushIndex;
use crate::timers::Timer;
use crate::widget::{WidgetArena, WidgetMut, WidgetRef, WidgetState};
use crate::{AccessEvent, Action, CursorIcon, Handled, QueryCtx, Widget, WidgetId, WidgetPod};

Expand Down Expand Up @@ -123,6 +125,7 @@ pub struct RenderRootOptions {

pub enum RenderRootSignal {
Action(Action, WidgetId),
TimerRequested(Timer),
StartIme,
EndIme,
ImeMoved(LogicalPosition<f64>, LogicalSize<f64>),
Expand Down Expand Up @@ -259,6 +262,14 @@ impl RenderRoot {
}

// --- MARK: PUB FUNCTIONS ---
pub fn handle_elapsed_timer(&mut self, timer: Timer) -> Handled {
let _span = info_span!("elapsed_timer");
let handled = run_on_elapsed_timer_pass(self, &timer);
self.run_rewrite_passes();

handled
}

pub fn handle_pointer_event(&mut self, event: PointerEvent) -> Handled {
let _span = info_span!("pointer_event");
let handled = run_on_pointer_event_pass(self, &event);
Expand Down
6 changes: 6 additions & 0 deletions masonry/src/testing/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::passes::anim::run_update_anim_pass;
use crate::render_root::{RenderRoot, RenderRootOptions, RenderRootSignal, WindowSizePolicy};
use crate::testing::screenshots::get_image_diff;
use crate::testing::snapshot_utils::get_cargo_workspace;
use crate::timers::TimerQueue;
use crate::tracing_backend::try_init_test_tracing;
use crate::widget::{WidgetMut, WidgetRef};
use crate::{Color, Handled, Point, Size, Vec2, Widget, WidgetId};
Expand Down Expand Up @@ -116,6 +117,7 @@ pub struct TestHarness {
has_ime_session: bool,
ime_rect: (LogicalPosition<f64>, LogicalSize<f64>),
title: String,
timers: TimerQueue,
}

/// Assert a snapshot of a rendered frame of your app.
Expand Down Expand Up @@ -199,6 +201,7 @@ impl TestHarness {
has_ime_session: false,
ime_rect: Default::default(),
title: String::new(),
timers: TimerQueue::new(),
};
harness.process_window_event(WindowEvent::Resize(window_size));

Expand Down Expand Up @@ -244,6 +247,9 @@ impl TestHarness {
RenderRootSignal::Action(action, widget_id) => {
self.action_queue.push_back((action, widget_id));
}
RenderRootSignal::TimerRequested(timer) => {
self.timers.push(timer);
}
RenderRootSignal::StartIme => {
self.has_ime_session = true;
}
Expand Down
Loading
Loading