Add a fractal clock example to showcase painting performance
This commit is contained in:
parent
5a9e3d62bf
commit
71154edf9b
21 changed files with 425 additions and 37 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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 {
|
||||
|
|
|
@ -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`?
|
||||
|
||||
|
|
|
@ -37,6 +37,16 @@ impl Frame {
|
|||
outline: Some(Outline::new(1.0, color::white(128))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_color(mut self, fill_color: Option<Color>) -> Self {
|
||||
self.fill_color = fill_color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn outline(mut self, outline: Option<Outline>) -> Self {
|
||||
self.outline = outline;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
7
emigui/src/examples.rs
Normal file
7
emigui/src/examples.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod app;
|
||||
mod fractal_clock;
|
||||
|
||||
pub use {
|
||||
app::{ExampleApp, ExampleWindow},
|
||||
fractal_clock::FractalClock,
|
||||
};
|
|
@ -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<Context>) {
|
||||
// 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,
|
194
emigui/src/examples/fractal_clock.rs
Normal file
194
emigui/src/examples/fractal_clock.rs
Normal file
|
@ -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<Context>, 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<f64>,
|
||||
|
||||
/// Files has been dropped into the window.
|
||||
pub dropped_files: Vec<std::path::PathBuf>,
|
||||
|
||||
|
@ -31,6 +37,9 @@ pub struct RawInput {
|
|||
|
||||
/// In-order events received this frame
|
||||
pub events: Vec<Event>,
|
||||
|
||||
/// Web-only input
|
||||
pub web: Option<Web>,
|
||||
}
|
||||
|
||||
/// 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<f64>,
|
||||
|
||||
/// Files has been dropped into the window.
|
||||
pub dropped_files: Vec<std::path::PathBuf>,
|
||||
|
||||
|
@ -82,9 +94,20 @@ pub struct GuiInput {
|
|||
|
||||
/// In-order events received this frame
|
||||
pub events: Vec<Event>,
|
||||
|
||||
/// Web-only input
|
||||
pub web: Option<Web>,
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -133,6 +133,12 @@ impl Hyperlink {
|
|||
url,
|
||||
}
|
||||
}
|
||||
|
||||
/// Show some other text than the url
|
||||
pub fn text(mut self, text: impl Into<String>) -> 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)
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
[dependencies]
|
||||
emigui = { path = "../emigui" }
|
||||
|
||||
chrono = { version = "0.4" }
|
||||
clipboard = "0.5"
|
||||
glium = "0.24"
|
||||
serde_json = "1"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<Context>,
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue