Add a fractal clock example to showcase painting performance

This commit is contained in:
Emil Ernerfeldt 2020-05-11 20:21:24 +02:00
parent 5a9e3d62bf
commit 71154edf9b
21 changed files with 425 additions and 37 deletions

35
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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.

View file

@ -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 {

View file

@ -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`?

View file

@ -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 {

View file

@ -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> {

View file

@ -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
View file

@ -0,0 +1,7 @@
mod app;
mod fractal_clock;
pub use {
app::{ExampleApp, ExampleWindow},
fractal_clock::FractalClock,
};

View file

@ -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,

View 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);
}
}
}

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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())

View file

@ -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
}

View file

@ -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)

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
emigui = { path = "../emigui" }
chrono = { version = "0.4" }
clipboard = "0.5"
glium = "0.24"
serde_json = "1"

View file

@ -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)
}

View file

@ -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();

View file

@ -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();