diff --git a/Cargo.lock b/Cargo.lock index e990c565..64d27a59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,6 +95,16 @@ dependencies = [ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clipboard" version = "0.5.0" @@ -189,6 +199,7 @@ dependencies = [ name = "emigui_glium" version = "0.1.0" dependencies = [ + "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "emigui 0.1.0", "glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -215,6 +226,9 @@ dependencies = [ "emigui 0.1.0", "emigui_glium 0.1.0", "glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -442,6 +456,15 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.11" @@ -745,6 +768,15 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ttf-parser" version = "0.5.0" @@ -1008,6 +1040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cc 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)" = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" +"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" "checksum clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" "checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" @@ -1042,6 +1075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" @@ -1079,6 +1113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" "checksum stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" "checksum syn 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" +"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" "checksum ttf-parser 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afa7e68b11bcf63b8a990bd7fe894dece47a629dbbba0fce4dbecb4dbac3ef8d" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/README.md b/README.md index a7b07773..96c8931d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Currently two backends have been tested: The same application code can thus be compiled to either into a native app or a web app. ## Demos -[Emigui feature demo](https://emilk.github.io/emigui/index.html), (partial) source: https://github.com/emilk/emigui/blob/master/emigui/src/example_app.rs +[Emigui feature demo](https://emilk.github.io/emigui/index.html), (partial) source: https://github.com/emilk/emigui/blob/master/emigui/src/examples/app.rs [Hobogo: A small game using Emigui](https://emilk.github.io/hobogo/index.html), source: https://github.com/emilk/hobogo diff --git a/build_glium.sh b/build_glium.sh index 9d8a008a..b7a3eec2 100755 --- a/build_glium.sh +++ b/build_glium.sh @@ -3,6 +3,6 @@ set -eu cargo fmt --all -- --check cargo check --all-features -cargo clean -p emigui && cargo clippy +cargo clippy cargo run --bin example_glium --release diff --git a/docs/example_wasm_bg.wasm b/docs/example_wasm_bg.wasm index ffd56c9a..611c673e 100644 Binary files a/docs/example_wasm_bg.wasm and b/docs/example_wasm_bg.wasm differ diff --git a/docs/index.html b/docs/index.html index 34d44aea..a207cde9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -107,7 +107,13 @@ screen_size: { x: window.innerWidth, y: window.innerHeight }, pixels_per_point: pixels_per_point(), time: window.performance.now() / 1000.0, + seconds_since_midnight: seconds_since_midnight(), events: g_events, + + web: { + location: window.location.toString(), + location_hash: window.location.hash.toString(), // i.e. #fragment + }, }; g_scroll_delta_x = 0; g_scroll_delta_y = 0; @@ -115,6 +121,11 @@ return input; } + function seconds_since_midnight() { + var d = new Date(); + return (d.getHours() * 60.0 + d.getMinutes()) * 60.0 + d.getSeconds() + 1e-3 * d.getMilliseconds(); + } + function mouse_pos_from_event(canvas, event) { var rect = canvas.getBoundingClientRect(); return { diff --git a/emigui/README.md b/emigui/README.md index 748c5c90..07c156f7 100644 --- a/emigui/README.md +++ b/emigui/README.md @@ -13,8 +13,9 @@ This is the core library crate Emigui. It is fully platform independent without * [x] Tooltip * [x] Movable/resizable windows * [x] Kinetic windows - * [ ] Windows should open from Regions and be boxed by parent region. + * [ ] Windows should open from `UI`s and be boxed by parent ui. * Then we could open the example app inside a window in the example app, recursively. + * [ ] Resize any side and corner on windows * [ ] Scroll areas * [x] Vertical scrolling * [ ] Horizontal scrolling @@ -22,7 +23,9 @@ This is the core library crate Emigui. It is fully platform independent without * [x] Drag background to scroll * [ ] Kinetic scrolling * [x] Add support for clicking links -* [ ] Menu bar (File, Edit, etc) +* [x] Menu bar (File, Edit, etc) + * [ ] Sub-menus + * [ ] Keyboard shortcuts * [ ] Text input * [x] Input events (key presses) * [x] Text focus @@ -35,7 +38,7 @@ This is the core library crate Emigui. It is fully platform independent without * [ ] Style editor * [ ] Table with resizable columns * [ ] Layout - * [ ] Generalize Layout (separate from Region) + * [ ] Generalize Layout (separate from Ui) * [ ] Cascading layout: same lite if it fits, else next line. Like text. * [ ] Grid layout * [ ] Image support @@ -46,12 +49,17 @@ This is the core library crate Emigui. It is fully platform independent without * [ ] Make it a JS library for easily creating your own stuff * [ ] Read url fragment and redirect to a subpage (e.g. different examples apps) +### Painting +* [ ] Pixel-perfect painting (round positions to nearest pixel). +* [ ] Make sure alpha blending is correct (different between web and glium) +* [ ] sRGBA correct colors + ### Animations Add extremely quick animations for some things, maybe 2-3 frames. For instance: * [x] Animate collapsing headers with clip_rect ### Clip rects -* [x] Separate Region::clip_rect from Region::rect +* [x] Separate Ui::clip_rect from Ui::rect * [x] Use clip rectangles when painting * [x] Use clip rectangles when interacting * [x] Adjust clip rects so edges of child widgets aren't clipped @@ -62,7 +70,7 @@ Add extremely quick animations for some things, maybe 2-3 frames. For instance: * [ ] `trait Container` (`Frame`, `Resize`, `ScrollArea`, ...) * [ ] `widget::TextButton` implemented as a `container::Button` which contains a `widget::Label`. * [ ] Easily chain `Container`s without nested closures. - * e.g. `region.containers((Frame::new(), Resize::new(), ScrollArea::new()), |ui| ...)` + * e.g. `ui.containers((Frame::new(), Resize::new(), ScrollArea::new()), |ui| ...)` ### Input * [ ] Distinguish between clicks and drags @@ -71,22 +79,19 @@ Add extremely quick animations for some things, maybe 2-3 frames. For instance: ### Debugability / Inspection * [x] Widget debug rectangles -* [ ] Easily debug why something keeps expanding - +* [x] Easily debug why something keeps expanding ### Other * [x] Persist UI state in external storage -* [ ] Pixel-perfect rendering (round positions to nearest pixel). -* [ ] Build in a profiler which tracks which region in which window takes up CPU. +* [ ] Persist Example App state +* [ ] Build in a profiler which tracks which `Ui` in which window takes up CPU. * [ ] Draw as flame graph * [ ] Draw as hotmap ### Names and structure * [ ] Rename things to be more consistent with Dear ImGui * [x] Combine Emigui and Context? -* [ ] Solve which parts of Context are behind a mutex - * [ ] All of Context behind one mutex? - * [ } Break up Context into Input, State, Output ? +* [x] Solve which parts of Context are behind a mutex * [x] Rename Region to Ui * [ ] Maybe find a shorter name for the library like `egui`? diff --git a/emigui/src/containers/frame.rs b/emigui/src/containers/frame.rs index 628f7fca..d4d6e276 100644 --- a/emigui/src/containers/frame.rs +++ b/emigui/src/containers/frame.rs @@ -37,6 +37,16 @@ impl Frame { outline: Some(Outline::new(1.0, color::white(128))), } } + + pub fn fill_color(mut self, fill_color: Option) -> Self { + self.fill_color = fill_color; + self + } + + pub fn outline(mut self, outline: Option) -> Self { + self.outline = outline; + self + } } impl Frame { diff --git a/emigui/src/containers/window.rs b/emigui/src/containers/window.rs index 3f39fcd1..131fd3f1 100644 --- a/emigui/src/containers/window.rs +++ b/emigui/src/containers/window.rs @@ -48,20 +48,27 @@ impl<'open> Window<'open> { self } - /// This is quite a crap idea /// Usage: `Winmdow::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))` + /// Not sure this is a good interface for this. pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self { mutate(&mut self); self } - /// This is quite a crap idea /// Usage: `Winmdow::new(...).resize(|r| r.auto_expand_width(true))` + /// Not sure this is a good interface for this. pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self { self.resize = mutate(self.resize); self } + /// Usage: `Winmdow::new(...).frame(|f| f.fill_color(Some(BLUE)))` + /// Not sure this is a good interface for this. + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = Some(frame); + self + } + pub fn default_pos(mut self, default_pos: Pos2) -> Self { self.area = self.area.default_pos(default_pos); self @@ -72,6 +79,10 @@ impl<'open> Window<'open> { self } + pub fn default_rect(self, rect: Rect) -> Self { + self.default_pos(rect.min).default_size(rect.size()) + } + pub fn min_size(mut self, min_size: Vec2) -> Self { self.resize = self.resize.min_size(min_size); self @@ -100,6 +111,13 @@ impl<'open> Window<'open> { self.scroll = None; self } + + pub fn scroll(mut self, scroll: bool) -> Self { + if !scroll { + self.scroll = None; + } + self + } } impl<'open> Window<'open> { diff --git a/emigui/src/context.rs b/emigui/src/context.rs index 42a06038..197d3b9c 100644 --- a/emigui/src/context.rs +++ b/emigui/src/context.rs @@ -80,6 +80,10 @@ impl Context { }) } + pub fn rect(&self) -> Rect { + Rect::from_min_size(pos2(0.0, 0.0), self.input.screen_size) + } + pub fn memory(&self) -> parking_lot::MutexGuard<'_, Memory> { self.memory.lock() } diff --git a/emigui/src/examples.rs b/emigui/src/examples.rs new file mode 100644 index 00000000..5b66984a --- /dev/null +++ b/emigui/src/examples.rs @@ -0,0 +1,7 @@ +mod app; +mod fractal_clock; + +pub use { + app::{ExampleApp, ExampleWindow}, + fractal_clock::FractalClock, +}; diff --git a/emigui/src/example_app.rs b/emigui/src/examples/app.rs similarity index 89% rename from emigui/src/example_app.rs rename to emigui/src/examples/app.rs index 7138f1f3..2bfad344 100644 --- a/emigui/src/example_app.rs +++ b/emigui/src/examples/app.rs @@ -3,37 +3,62 @@ use std::sync::Arc; use serde_derive::{Deserialize, Serialize}; -use crate::{color::*, containers::*, widgets::*, *}; +use crate::{color::*, containers::*, examples::FractalClock, widgets::*, *}; // ---------------------------------------------------------------------------- #[derive(Default, Deserialize, Serialize)] pub struct ExampleApp { + has_initialized: bool, example_window: ExampleWindow, open_windows: OpenWindows, + fractal_clock: FractalClock, } impl ExampleApp { pub fn ui(&mut self, ctx: &Arc) { // TODO: Make it even simpler to show a window + + // TODO: window manager for automatic positioning? + + let ExampleApp { + has_initialized, + example_window, + open_windows, + fractal_clock, + } = self; + + if !*has_initialized { + // #fragment end of URL: + let location_hash = ctx + .input() + .web + .as_ref() + .map(|web| web.location_hash.as_str()); + if location_hash == Some("#clock") { + open_windows.fractal_clock = true; + } + *has_initialized = true; + } + Window::new("Examples") .default_pos(pos2(32.0, 100.0)) .default_size(vec2(430.0, 600.0)) .show(ctx, |ui| { - show_menu_bar(ui, &mut self.open_windows); - self.example_window.ui(ui); + show_menu_bar(ui, open_windows); + example_window.ui(ui); }); Window::new("Settings") - .open(&mut self.open_windows.settings) + .open(&mut open_windows.settings) .default_pos(pos2(500.0, 100.0)) - .default_size(vec2(350.0, 200.0)) + .default_size(vec2(350.0, 400.0)) .show(ctx, |ui| { ctx.settings_ui(ui); }); Window::new("Inspection") - .open(&mut self.open_windows.inspection) + .open(&mut open_windows.inspection) .default_pos(pos2(500.0, 400.0)) .default_size(vec2(400.0, 300.0)) .show(ctx, |ui| { @@ -41,12 +66,14 @@ impl ExampleApp { }); Window::new("Memory") - .open(&mut self.open_windows.memory) + .open(&mut open_windows.memory) .default_pos(pos2(700.0, 350.0)) .auto_sized() .show(ctx, |ui| { ctx.memory_ui(ui); }); + + fractal_clock.window(ctx, &mut open_windows.fractal_clock); } } @@ -55,6 +82,7 @@ struct OpenWindows { settings: bool, inspection: bool, memory: bool, + fractal_clock: bool, } impl Default for OpenWindows { @@ -63,6 +91,7 @@ impl Default for OpenWindows { settings: false, inspection: true, memory: false, + fractal_clock: false, } } } @@ -75,9 +104,13 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows) { ui.add(Button::new("Don't Quit")); }); menu::menu(ui, "Windows", |ui| { + // TODO: open on top when clicking a new. + // Maybe an Window or Area can detect that: if wasn't open last frame, but is now, + // then automatically go to front? ui.add(Checkbox::new(&mut windows.settings, "Settings")); ui.add(Checkbox::new(&mut windows.inspection, "Inspection")); ui.add(Checkbox::new(&mut windows.memory, "Memory")); + ui.add(Checkbox::new(&mut windows.fractal_clock, "Fractal Clock")); }); menu::menu(ui, "About", |ui| { ui.add(label!("This is Emigui, but you already knew that!")); @@ -325,7 +358,7 @@ impl Painting { for line in &self.lines { if line.len() >= 2 { - ui.add_paint_cmd(PaintCmd::Line { + ui.add_paint_cmd(PaintCmd::LinePath { points: line.iter().map(|p| canvas_corner + *p).collect(), color: LIGHT_GRAY, width: 2.0, diff --git a/emigui/src/examples/fractal_clock.rs b/emigui/src/examples/fractal_clock.rs new file mode 100644 index 00000000..c0c3da8a --- /dev/null +++ b/emigui/src/examples/fractal_clock.rs @@ -0,0 +1,194 @@ +use std::sync::Arc; + +use serde_derive::{Deserialize, Serialize}; + +use crate::{containers::*, widgets::*, *}; + +#[derive(Deserialize, Serialize)] +#[serde(default)] +pub struct FractalClock { + paused: bool, + time: f64, + zoom: f32, + start_line_width: f32, + depth: usize, + length_factor: f32, + luminance_factor: f32, + width_factor: f32, +} + +impl Default for FractalClock { + fn default() -> Self { + Self { + paused: false, + time: 0.0, + zoom: 0.25, + start_line_width: 2.5, + depth: 9, + length_factor: 0.8, + luminance_factor: 0.8, + width_factor: 0.9, + } + } +} + +impl FractalClock { + pub fn window(&mut self, ctx: &Arc, open: &mut bool) { + Window::new("FractalClock") + .open(open) + .default_rect(ctx.rect().expand(-40.0)) + .scroll(false) + // Dark background frame to make it pop: + .frame(Frame::window(&ctx.style()).fill_color(Some(color::black(250)))) + .show(ctx, |ui| self.ui(ui)); + } + + pub fn ui(&mut self, ui: &mut Ui) { + self.fractal_ui(ui); + + // TODO: background frame etc + Frame::popup(ui.style()) + .fill_color(Some(color::gray(34, 160))) + .outline(None) + .show(&mut ui.left_column(320.0), |ui| self.options_ui(ui)); + } + + fn options_ui(&mut self, ui: &mut Ui) { + let time = if let Some(seconds_since_midnight) = ui.input().seconds_since_midnight { + ui.add(label!( + "Local time: {:02}:{:02}:{:02}.{:03}", + (seconds_since_midnight.rem_euclid(24.0 * 60.0 * 60.0) / 3600.0).floor(), + (seconds_since_midnight.rem_euclid(60.0 * 60.0) / 60.0).floor(), + (seconds_since_midnight.rem_euclid(60.0)).floor(), + (seconds_since_midnight.rem_euclid(1.0) * 1000.0).floor() + )); + seconds_since_midnight + } else { + ui.add(label!( + "The fractal_clock clock is not showing the correct time" + )); + ui.input().time + }; + + ui.add(Checkbox::new(&mut self.paused, "Paused")); + ui.add(Slider::f32(&mut self.zoom, 0.0..=1.0).text("zoom")); + ui.add(Slider::f32(&mut self.start_line_width, 0.0..=5.0).text("Start line width")); + ui.add(Slider::usize(&mut self.depth, 0..=14).text("depth")); + ui.add(Slider::f32(&mut self.length_factor, 0.0..=1.0).text("length factor")); + ui.add(Slider::f32(&mut self.luminance_factor, 0.0..=1.0).text("luminance factor")); + ui.add(Slider::f32(&mut self.width_factor, 0.0..=1.0).text("width factor")); + if ui.add(Button::new("Reset")).clicked { + *self = Default::default(); + } + + if !self.paused { + self.time = time; + } + + ui.add( + Hyperlink::new("http://www.dqd.com/~mayoff/programs/FractalClock/") + .text("Inspired by a screensaver by Rob Mayoff"), + ); + } + + fn fractal_ui(&mut self, ui: &mut Ui) { + struct Hand { + length: f32, + angle: f32, + vec: Vec2, + } + + impl Hand { + fn from_length_angle(length: f32, angle: f32) -> Self { + Self { + length, + angle, + vec: length * Vec2::angled(angle), + } + } + } + + let angle_from_period = + |period| TAU * (self.time.rem_euclid(period) / period) as f32 - TAU / 4.0; + + let hands = [ + // Second hand: + Hand::from_length_angle(self.length_factor, angle_from_period(60.0)), + // Minute hand: + Hand::from_length_angle(self.length_factor, angle_from_period(60.0 * 60.0)), + // Hour hand: + Hand::from_length_angle(0.5, angle_from_period(12.0 * 60.0 * 60.0)), + ]; + + let rect = ui.available_rect_min(); + + let scale = self.zoom * rect.width().min(rect.height()); + let mut paint_line = |points: [Pos2; 2], color: Color, width: f32| { + let line = [ + rect.center() + scale * points[0].to_vec2(), + rect.center() + scale * points[1].to_vec2(), + ]; + + ui.add_paint_cmd(PaintCmd::line_segment([line[0], line[1]], color, width)); + }; + + let hand_rotations = [ + hands[0].angle - hands[2].angle, + hands[1].angle - hands[2].angle, + ]; + + let hand_rotors = [ + hands[0].length * Vec2::angled(hand_rotations[0]), + hands[1].length * Vec2::angled(hand_rotations[1]), + ]; + + #[derive(Clone, Copy)] + struct Node { + pos: Pos2, + dir: Vec2, + } + + let mut nodes = Vec::new(); + + let mut width = self.start_line_width; + + for (i, hand) in hands.iter().enumerate() { + let center = pos2(0.0, 0.0); + let end = center + hand.vec; + paint_line([center, end], color::additive_gray(255), width); + if i < 2 { + nodes.push(Node { + pos: end, + dir: hand.vec, + }); + } + } + + let mut luminance = 0.7; // Start dimmer than main hands + + let mut new_nodes = Vec::new(); + for _ in 0..self.depth { + new_nodes.clear(); + new_nodes.reserve(nodes.len() * 2); + + luminance *= self.luminance_factor; + width *= self.width_factor; + + let luminance_u8 = (255.0 * luminance).round() as u8; + + for rotor in &hand_rotors { + for a in &nodes { + let new_dir = rotor.rotate_other(a.dir); + let b = Node { + pos: a.pos + new_dir, + dir: new_dir, + }; + paint_line([a.pos, b.pos], color::additive_gray(luminance_u8), width); + new_nodes.push(b); + } + } + + std::mem::swap(&mut nodes, &mut new_nodes); + } + } +} diff --git a/emigui/src/input.rs b/emigui/src/input.rs index 4c899942..d154a45f 100644 --- a/emigui/src/input.rs +++ b/emigui/src/input.rs @@ -1,8 +1,10 @@ +use serde_derive::Deserialize; + use crate::math::*; /// What the integration gives to the gui. /// All coordinates in emigui is in point/logical coordinates. -#[derive(Clone, Debug, Default, serde_derive::Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] #[serde(default)] pub struct RawInput { /// Is the button currently down? @@ -15,6 +17,7 @@ pub struct RawInput { pub scroll_delta: Vec2, /// Size of the screen in points. + /// TODO: this should be screen_rect for easy sandboxing. pub screen_size: Vec2, /// Also known as device pixel ratio, > 1 for HDPI screens. @@ -23,6 +26,9 @@ pub struct RawInput { /// Time in seconds. Relative to whatever. Used for animation. pub time: f64, + /// Local time. Only used for the clock in the example app. + pub seconds_since_midnight: Option, + /// Files has been dropped into the window. pub dropped_files: Vec, @@ -31,6 +37,9 @@ pub struct RawInput { /// In-order events received this frame pub events: Vec, + + /// Web-only input + pub web: Option, } /// What emigui maintains @@ -74,6 +83,9 @@ pub struct GuiInput { /// Time since last frame, in seconds. pub dt: f32, + /// Local time. Only used for the clock in the example app. + pub seconds_since_midnight: Option, + /// Files has been dropped into the window. pub dropped_files: Vec, @@ -82,9 +94,20 @@ pub struct GuiInput { /// In-order events received this frame pub events: Vec, + + /// Web-only input + pub web: Option, } -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde_derive::Deserialize)] +#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Deserialize)] +#[serde(default)] +pub struct Web { + pub location: String, + /// i.e. "#fragment" + pub location_hash: String, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Event { Copy, @@ -97,7 +120,7 @@ pub enum Event { }, } -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde_derive::Deserialize)] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Key { Alt, @@ -145,9 +168,11 @@ impl GuiInput { pixels_per_point: new.pixels_per_point, time: new.time, dt, + seconds_since_midnight: new.seconds_since_midnight, dropped_files: new.dropped_files.clone(), hovered_files: new.hovered_files.clone(), events: new.events.clone(), + web: new.web.clone(), } } } @@ -166,6 +191,9 @@ impl RawInput { ui.add(label!("events: {:?}", self.events)); ui.add(label!("dropped_files: {:?}", self.dropped_files)); ui.add(label!("hovered_files: {:?}", self.hovered_files)); + if let Some(web) = &self.web { + web.ui(ui); + } } } @@ -189,5 +217,16 @@ impl GuiInput { ui.add(label!("events: {:?}", self.events)); ui.add(label!("dropped_files: {:?}", self.dropped_files)); ui.add(label!("hovered_files: {:?}", self.hovered_files)); + if let Some(web) = &self.web { + web.ui(ui); + } + } +} + +impl Web { + pub fn ui(&self, ui: &mut crate::Ui) { + use crate::label; + ui.add(label!("location: '{}'", self.location)); + ui.add(label!("location_hash: '{}'", self.location_hash)); } } diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index fe1a0d4f..fdd511b6 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -25,7 +25,7 @@ pub mod color; pub mod containers; mod context; -pub mod example_app; +pub mod examples; mod font; mod fonts; mod id; diff --git a/emigui/src/math.rs b/emigui/src/math.rs index 2f1c2a85..a8e638ea 100644 --- a/emigui/src/math.rs +++ b/emigui/src/math.rs @@ -65,6 +65,16 @@ impl Vec2 { vec2(angle.cos(), angle.sin()) } + /// Use this vector as a rotor, rotating something else. + /// Example: Vec2::angled(angle).rotate_other(some_vec) + #[must_use] + pub fn rotate_other(self, v: Vec2) -> Self { + Self { + x: v.x * self.x + v.y * -self.y, + y: v.x * self.y + v.y * self.x, + } + } + #[must_use] pub fn floor(self) -> Self { vec2(self.x.floor(), self.y.floor()) diff --git a/emigui/src/ui.rs b/emigui/src/ui.rs index c1bf6742..9fffcc74 100644 --- a/emigui/src/ui.rs +++ b/emigui/src/ui.rs @@ -256,6 +256,10 @@ impl Ui { self.finite_bottom_right() - self.cursor } + pub fn available_rect_min(&self) -> Rect { + Rect::from_min_size(self.cursor, self.available_space_min()) + } + pub fn direction(&self) -> Direction { self.dir } diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index 1e036c4f..f699c034 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -133,6 +133,12 @@ impl Hyperlink { url, } } + + /// Show some other text than the url + pub fn text(mut self, text: impl Into) -> Self { + self.text = text.into(); + self + } } impl Widget for Hyperlink { @@ -154,6 +160,7 @@ impl Widget for Hyperlink { if interact.hovered { // Underline: + // TODO: underline spaces between words too. for fragment in &text { let pos = interact.rect.min; let y = pos.y + fragment.y_offset + line_spacing; @@ -260,7 +267,7 @@ impl<'a> Widget for Checkbox<'a> { let id = ui.make_position_id(); let text_style = TextStyle::Button; let font = &ui.fonts()[text_style]; - let (text, text_size) = font.layout_multiline(&self.text, ui.available_width()); + let (text, text_size) = font.layout_single_line(&self.text); let interact = ui.reserve_space( ui.style().button_padding + vec2(ui.style().start_icon_width, 0.0) diff --git a/emigui_glium/Cargo.toml b/emigui_glium/Cargo.toml index 05f08c06..9fff8085 100644 --- a/emigui_glium/Cargo.toml +++ b/emigui_glium/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] emigui = { path = "../emigui" } +chrono = { version = "0.4" } clipboard = "0.5" glium = "0.24" serde_json = "1" diff --git a/emigui_glium/src/lib.rs b/emigui_glium/src/lib.rs index f3fbc2df..7b66c6ba 100644 --- a/emigui_glium/src/lib.rs +++ b/emigui_glium/src/lib.rs @@ -195,3 +195,12 @@ pub fn write_memory( serde_json::to_writer_pretty(std::fs::File::create(memory_json_path)?, &*ctx.memory())?; Ok(()) } + +// ---------------------------------------------------------------------------- + +/// Time of day as seconds since midnight. Used for clock in example app. +pub fn local_time_of_day() -> f64 { + use chrono::Timelike; + let time = chrono::Local::now().time(); + time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64) +} diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index f9b4b94c..1caaa711 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -4,7 +4,7 @@ use std::time::{Duration, Instant}; use { - emigui::{example_app::ExampleApp, widgets::*, *}, + emigui::{examples::ExampleApp, widgets::*, *}, glium::glutin, }; @@ -79,7 +79,7 @@ fn main() { let mut running = true; let mut frame_start = Instant::now(); let mut frame_times = emigui::MovementTracker::new(1000, 1.0); - let mut example_app = ExampleApp::default(); + let mut examples = ExampleApp::default(); let mut clipboard = emigui_glium::init_clipboard(); emigui_glium::read_memory(&ctx, memory_path); @@ -96,6 +96,7 @@ fn main() { { raw_input.time = start_time.elapsed().as_nanos() as f64 * 1e-9; + raw_input.seconds_since_midnight = Some(emigui_glium::local_time_of_day()); raw_input.scroll_delta = vec2(0.0, 0.0); raw_input.dropped_files.clear(); raw_input.hovered_files.clear(); @@ -130,7 +131,7 @@ fn main() { .text_style(TextStyle::Monospace), ); - example_app.ui(&ctx); + examples.ui(&ctx); let (output, paint_batches) = ctx.end_frame(); diff --git a/example_wasm/src/lib.rs b/example_wasm/src/lib.rs index e6e9d22d..a14d1f17 100644 --- a/example_wasm/src/lib.rs +++ b/example_wasm/src/lib.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use { emigui::{ - color::srgba, example_app::ExampleApp, label, widgets::Separator, Align, RawInput, - TextStyle, *, + color::srgba, examples::ExampleApp, label, widgets::Separator, Align, RawInput, TextStyle, + *, }, emigui_wasm::now_sec, }; @@ -15,7 +15,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct State { - example_app: ExampleApp, + examples: ExampleApp, ctx: Arc, webgl_painter: emigui_wasm::webgl::Painter, @@ -27,7 +27,7 @@ impl State { let ctx = Context::new(pixels_per_point); emigui_wasm::load_memory(&ctx); Ok(State { - example_app: Default::default(), + examples: Default::default(), ctx, webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?, frame_times: emigui::MovementTracker::new(1000, 1.0), @@ -77,7 +77,7 @@ impl State { .text_style(TextStyle::Monospace), ); - self.example_app.ui(&self.ctx); + self.examples.ui(&self.ctx); let bg_color = srgba(0, 0, 0, 0); // Use background css color. let (output, batches) = self.ctx.end_frame();