#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[cfg(not(windows))]
fn main() {
eprintln!("this example needs Windows");
}
#[cfg(windows)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
app::run()
}
#[cfg(windows)]
mod app {
use std::time::Instant;
use denise::{ElementState, InputEvent, Modifiers, Radius, Rect, Role, Size, Surface, Theme};
use denise_ui::widgets::{Align, Button, Label, Panel, TextInput};
use denise_ui::{NodeId, Ui};
use denise_win32::{ControlDelegate, DeniseControl, DibSurface};
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::WindowsAndMessaging::{
AdjustWindowRect, CW_USEDEFAULT, CreateWindowExW, DefWindowProcW, DispatchMessageW,
GetMessageW, MSG, PostQuitMessage, RegisterClassExW, SW_SHOW, SWP_NOZORDER, SetTimer,
SetWindowPos, ShowWindow, TranslateMessage, WINDOW_EX_STYLE, WM_DESTROY, WM_SIZE, WM_TIMER,
WNDCLASSEXW, WS_OVERLAPPEDWINDOW,
};
use windows::core::w;
const WIDTH: i32 = 520;
const HEIGHT: i32 = 400;
const TICK_MS: u32 = 20;
const TIMER_ID: usize = 1;
static mut CONTROL: Option<DeniseControl> = None;
#[derive(Clone, Debug, PartialEq, Eq)]
enum Msg {
Save,
Reset,
}
struct Demo {
ui: Ui<Msg>,
status: NodeId,
keys: NodeId,
text: NodeId,
pointer: NodeId,
name: NodeId,
started: Instant,
saves: u32,
last_damage: usize,
}
impl Demo {
fn new(size: Size) -> Self {
let mut ui: Ui<Msg> = Ui::new(size, Theme::DARK);
ui.show_cursor(false);
let root = ui.root();
let card = ui
.add(
root,
Panel::default().with_radius(Radius::Box),
Rect::new(28, 28, size.width as i32 - 56, size.height as i32 - 56),
)
.expect("card");
ui.add(
card,
Label::new("Operatørpanel").with_size(22),
Rect::new(24, 20, 380, 30),
)
.expect("title");
let name = ui
.add(
card,
TextInput::<Msg>::new()
.with_placeholder("Ola Nordmann")
.with_submit(Msg::Save),
Rect::new(24, 70, 380, 40),
)
.expect("name");
ui.add(
card,
Button::new("Lagre", Msg::Save),
Rect::new(24, 130, 180, 44),
)
.expect("save");
ui.add(
card,
Button::new("Nullstill", Msg::Reset).with_role(Role::Neutral),
Rect::new(220, 130, 180, 44),
)
.expect("reset");
let status = ui
.add(
card,
Label::new("Tab, Enter, click.")
.with_role(Role::Base300)
.with_align(Align::Center, Align::Center),
Rect::new(24, 186, 380, 22),
)
.expect("status");
let mut line = |y: i32, text: &str| {
ui.add(
card,
Label::new(text).with_role(Role::Base300).with_size(13),
Rect::new(24, y, 380, 18),
)
.expect("diagnostic line")
};
let keys = line(214, "key -");
let text = line(234, "text -");
let pointer = line(254, "mouse -");
ui.focus(Some(name));
Self {
ui,
status,
keys,
text,
pointer,
name,
started: Instant::now(),
saves: 0,
last_damage: 0,
}
}
fn set(&mut self, node: NodeId, text: String) {
if let Some(label) = self.ui.widget_mut::<Label>(node) {
label.update(&text);
}
}
fn describe(&mut self, events: &[InputEvent]) {
for event in events {
match event {
InputEvent::Key {
code,
state,
repeat,
modifiers,
} => {
let mut held = String::new();
for (bit, name) in [
(Modifiers::SHIFT, "shift"),
(Modifiers::CTRL, "ctrl"),
(Modifiers::ALT, "alt"),
(Modifiers::SUPER, "win"),
] {
if modifiers.contains(bit) {
held.push(' ');
held.push_str(name);
}
}
let updown = if state == &ElementState::Down {
"v"
} else {
"^"
};
let rep = if *repeat { " (repeat)" } else { "" };
self.set(self.keys, format!("key {updown} {code:?}{held}{rep}"));
}
InputEvent::Text { ch } => {
self.set(self.text, format!("text {ch:?} U+{:04X}", *ch as u32));
}
InputEvent::PointerMoved { position } => {
let damage = self.last_damage;
self.set(
self.pointer,
format!("mouse {},{} damage {damage}", position.x, position.y),
);
}
InputEvent::PointerScroll { delta_y, .. } => {
self.set(self.pointer, format!("wheel {delta_y:+.0}"));
}
InputEvent::PointerLeft => {
self.set(self.pointer, "mouse left the control".to_owned());
}
_ => {}
}
}
}
}
impl ControlDelegate for Demo {
fn update(
&mut self,
surface: &mut DibSurface,
events: &[InputEvent],
damage: &mut Vec<Rect>,
) {
if surface.size() != self.ui.size() {
self.ui = Demo::new(surface.size()).ui;
self.ui.invalidate_all();
}
self.describe(events);
self.ui.handle(events);
self.ui.tick(self.started.elapsed().as_millis() as u64);
let messages: Vec<Msg> = self.ui.drain_messages().collect();
for message in messages {
let text = match message {
Msg::Save => {
self.saves += 1;
let who = self
.ui
.widget::<TextInput<Msg>>(self.name)
.map(|f| f.text().to_owned())
.unwrap_or_default();
format!(
"Lagret {} ({})",
self.saves,
if who.is_empty() { "ingen" } else { &who }
)
}
Msg::Reset => {
if let Some(field) = self.ui.widget_mut::<TextInput<Msg>>(self.name) {
field.clear();
}
"Nullstilt.".to_owned()
}
};
if let Some(label) = self.ui.widget_mut::<Label>(self.status) {
label.set_text(text);
}
}
if !self.ui.needs_paint() {
return;
}
match surface.acquire() {
Ok(mut frame) => {
self.ui.paint(&mut frame);
drop(frame);
damage.extend_from_slice(self.ui.damage());
self.last_damage = self.ui.damage().len();
self.ui.presented();
}
Err(e) => eprintln!("acquire: {e}"),
}
}
fn next_wake_ms(&self) -> Option<u64> {
self.ui.next_wake_ms()
}
}
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
let instance = unsafe { GetModuleHandleW(None) }?;
let class = WNDCLASSEXW {
cbSize: size_of::<WNDCLASSEXW>() as u32,
lpfnWndProc: Some(host_proc),
hInstance: instance.into(),
lpszClassName: w!("Denise.Example.Host"),
..Default::default()
};
if unsafe { RegisterClassExW(&class) } == 0 {
return Err("could not register the host class".into());
}
let mut wanted = RECT {
left: 0,
top: 0,
right: WIDTH,
bottom: HEIGHT,
};
unsafe { AdjustWindowRect(&mut wanted, WS_OVERLAPPEDWINDOW, false) }?;
let window = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
w!("Denise.Example.Host"),
w!("Denise — embedded control"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
wanted.right - wanted.left,
wanted.bottom - wanted.top,
None,
None,
Some(instance.into()),
None,
)
}?;
let control = DeniseControl::new(
window,
Rect::new(0, 0, WIDTH, HEIGHT),
1.0,
Box::new(Demo::new(Size::new(WIDTH as u32, HEIGHT as u32))),
)?;
unsafe { CONTROL = Some(control) };
control.update();
unsafe { SetTimer(Some(window), TIMER_ID, TICK_MS, None) };
unsafe {
let _ = ShowWindow(window, SW_SHOW);
}
let mut message = MSG::default();
while unsafe { GetMessageW(&mut message, None, 0, 0) }.as_bool() {
unsafe {
let _ = TranslateMessage(&message);
DispatchMessageW(&message);
}
}
Ok(())
}
extern "system" fn host_proc(
hwnd: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
match message {
WM_SIZE => {
let width = (lparam.0 & 0xFFFF) as i32;
let height = ((lparam.0 >> 16) & 0xFFFF) as i32;
if let Some(control) = unsafe { CONTROL } {
unsafe {
let _ =
SetWindowPos(control.hwnd(), None, 0, 0, width, height, SWP_NOZORDER);
}
}
LRESULT(0)
}
WM_TIMER => {
if let Some(control) = unsafe { CONTROL } {
control.update();
}
LRESULT(0)
}
WM_DESTROY => {
unsafe { PostQuitMessage(0) };
LRESULT(0)
}
_ => unsafe { DefWindowProcW(hwnd, message, wparam, lparam) },
}
}
}