use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::{ElementState, MouseButton, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId};
use uzor::input::core::coordinator::{InputCoordinator, LayerId};
use uzor::input::core::sense::Sense;
use uzor::input::core::widget_kind::WidgetKind;
use uzor::input::pointer::state::{InputState, MouseButton as UzorBtn};
use uzor::types::{Rect, WidgetId, unsafe_widget_id};
use vello::kurbo::{Affine, RoundedRect};
use vello::peniko::{Color, Fill};
use vello::{AaConfig, RenderParams, Renderer, RendererOptions, Scene};
use vello::util::{RenderContext, RenderSurface};
const WIN_W: u32 = 320;
const WIN_H: u32 = 200;
const BG: Color = Color::from_rgb8(22, 22, 30);
const BTN_NORMAL: Color = Color::from_rgb8(55, 105, 175);
const BTN_HOVER: Color = Color::from_rgb8(85, 145, 220);
const BTN_PRESSED: Color = Color::from_rgb8(30, 65, 120);
const BTN_BORDER: Color = Color::from_rgb8(110, 170, 255);
struct MyButton {
id: WidgetId,
rect: Rect,
hovered: bool,
pressed: bool,
clicks: u32,
}
impl MyButton {
fn new(id: &str, rect: Rect) -> Self {
Self {
id: unsafe_widget_id(id),
rect,
hovered: false,
pressed: false,
clicks: 0,
}
}
fn register(&self, coord: &mut InputCoordinator, layer: &LayerId) {
coord.register_atomic(
self.id.clone(),
WidgetKind::Custom,
self.rect,
Sense::CLICK | Sense::HOVER,
layer,
);
}
fn update_from_responses(&mut self, responses: &[(WidgetId, uzor::input::core::response::WidgetResponse)]) {
for (id, resp) in responses {
if *id == self.id {
self.hovered = resp.hovered;
if resp.clicked {
self.clicks += 1;
self.pressed = false;
}
}
}
}
fn set_pressed(&mut self, pressed: bool) {
if self.hovered {
self.pressed = pressed;
} else {
self.pressed = false;
}
}
fn draw(&self, scene: &mut Scene) {
let color = if self.pressed {
BTN_PRESSED
} else if self.hovered {
BTN_HOVER
} else {
BTN_NORMAL
};
let r = &self.rect;
let (rw, rh) = (r.width, r.height);
let shape = RoundedRect::new(r.x, r.y, r.x + rw, r.y + rh, 6.0);
scene.fill(Fill::NonZero, Affine::IDENTITY, color, None, &shape);
let border_shape = RoundedRect::new(
r.x - 1.0, r.y - 1.0,
r.x + rw + 1.0, r.y + rh + 1.0,
7.0,
);
scene.fill(Fill::NonZero, Affine::IDENTITY, BTN_BORDER, None, &border_shape);
scene.fill(Fill::NonZero, Affine::IDENTITY, color, None, &shape);
let bar_w = ((self.clicks as f64 * 10.0).min(rw - 8.0)).max(0.0);
if bar_w > 0.0 {
let bar = RoundedRect::new(
r.x + 4.0,
r.y + rh - 8.0,
r.x + 4.0 + bar_w,
r.y + rh - 4.0,
2.0,
);
scene.fill(Fill::NonZero, Affine::IDENTITY, Color::WHITE, None, &bar);
}
let dot_r = 4.0;
let cx = r.x + rw / 2.0;
let cy = r.y + rh / 2.0 - 4.0; let dot = vello::kurbo::Circle::new((cx, cy), dot_r);
let dot_color = if self.pressed {
Color::from_rgb8(255, 255, 255)
} else {
Color::from_rgb8(200, 230, 255)
};
scene.fill(Fill::NonZero, Affine::IDENTITY, dot_color, None, &dot);
}
}
struct AppState {
window: Arc<Window>,
render_cx: RenderContext,
surface: RenderSurface<'static>,
renderer: Renderer,
scene: Scene,
coord: InputCoordinator,
layer: LayerId,
button: MyButton,
mouse_pos: Option<(f64, f64)>,
clicked: Option<UzorBtn>,
button_down: bool,
}
impl AppState {
fn render(&mut self) {
let (width, height) = {
let s = &self.surface;
(s.config.width, s.config.height)
};
let mut input = InputState::default();
input.pointer.pos = self.mouse_pos;
input.pointer.clicked = self.clicked.take();
if self.button_down {
input.pointer.button_down = Some(UzorBtn::Left);
}
self.coord.begin_frame(input);
self.button.register(&mut self.coord, &self.layer);
self.scene = Scene::new();
self.scene.fill(
Fill::NonZero,
Affine::IDENTITY,
BG,
None,
&vello::kurbo::Rect::new(0.0, 0.0, width as f64, height as f64),
);
self.button.draw(&mut self.scene);
let responses = self.coord.end_frame();
self.button.update_from_responses(&responses);
self.button.set_pressed(self.button_down);
let dev = &self.render_cx.devices[self.surface.dev_id];
let render_params = RenderParams {
base_color: BG,
width,
height,
antialiasing_method: AaConfig::Area,
};
self.renderer
.render_to_texture(
&dev.device,
&dev.queue,
&self.scene,
&self.surface.target_view,
&render_params,
)
.unwrap_or_default();
let surface_texture = match self.surface.surface.get_current_texture() {
Ok(t) => t,
Err(_) => return,
};
let surface_view = surface_texture
.texture
.create_view(&vello::wgpu::TextureViewDescriptor::default());
let mut encoder = dev
.device
.create_command_encoder(&vello::wgpu::CommandEncoderDescriptor {
label: Some("level1-blit"),
});
self.surface
.blitter
.copy(&dev.device, &mut encoder, &self.surface.target_view, &surface_view);
dev.queue.submit([encoder.finish()]);
surface_texture.present();
self.window.request_redraw();
}
}
struct Handler {
state: Option<AppState>,
}
impl ApplicationHandler for Handler {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.state.is_some() {
return;
}
let attrs = Window::default_attributes()
.with_title("uzor L1 — bare hit-tester")
.with_inner_size(winit::dpi::LogicalSize::new(WIN_W, WIN_H))
.with_resizable(false);
let window = Arc::new(
event_loop
.create_window(attrs)
.expect("OS should be able to create a window on a desktop machine"),
);
let mut render_cx = RenderContext::new();
let size = window.inner_size();
let surface: RenderSurface<'static> = pollster::block_on(async {
render_cx
.create_surface(
Arc::clone(&window),
size.width.max(1),
size.height.max(1),
vello::wgpu::PresentMode::AutoVsync,
)
.await
.expect("vello should be able to create a GPU surface on a desktop machine")
});
let renderer = Renderer::new(
&render_cx.devices[surface.dev_id].device,
RendererOptions {
antialiasing_support: vello::AaSupport::area_only(),
num_init_threads: None,
..RendererOptions::default()
},
)
.expect("vello renderer creation should succeed");
window.request_redraw();
let btn_w = 140.0_f64;
let btn_h = 44.0_f64;
let btn_x = (WIN_W as f64 - btn_w) / 2.0;
let btn_y = (WIN_H as f64 - btn_h) / 2.0;
self.state = Some(AppState {
window,
render_cx,
surface,
renderer,
scene: Scene::new(),
coord: InputCoordinator::new(),
layer: LayerId::main(),
button: MyButton::new("demo_btn", Rect::new(btn_x, btn_y, btn_w, btn_h)),
mouse_pos: None,
clicked: None,
button_down: false,
});
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_id: WindowId,
event: WindowEvent,
) {
let Some(ref mut app) = self.state else {
return;
};
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::CursorMoved { position, .. } => {
app.mouse_pos = Some((position.x, position.y));
app.window.request_redraw();
}
WindowEvent::CursorLeft { .. } => {
app.mouse_pos = None;
app.window.request_redraw();
}
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
app.button_down = true;
app.window.request_redraw();
}
WindowEvent::MouseInput {
state: ElementState::Released,
button: MouseButton::Left,
..
} => {
app.button_down = false;
app.clicked = Some(UzorBtn::Left);
app.window.request_redraw();
}
WindowEvent::RedrawRequested => {
app.render();
}
_ => {}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(ref app) = self.state {
app.window.request_redraw();
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Wait);
let mut handler = Handler { state: None };
event_loop.run_app(&mut handler)?;
Ok(())
}