Refactor example code
This commit is contained in:
parent
4889296f7a
commit
d999962602
13 changed files with 402 additions and 343 deletions
4
TODO.md
4
TODO.md
|
@ -5,6 +5,10 @@ Maybe we can use build.rs to generate needed stuff, e.g. index.hmtl?
|
||||||
# Code
|
# Code
|
||||||
* Read TTF from browser?
|
* Read TTF from browser?
|
||||||
* requestAnimationFrame
|
* requestAnimationFrame
|
||||||
|
* Rename region to something shorter
|
||||||
|
* `region: &Region` `region.add(...)` :/
|
||||||
|
* `gui: &Gui` `gui.add(...)` :)
|
||||||
|
* `ui: &Ui` `ui.add(...)` :)
|
||||||
|
|
||||||
# Additional bindings, e.g. Piston
|
# Additional bindings, e.g. Piston
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<!-- TODO: read https://www.html5rocks.com/en/mobile/mobifying/#toc-meta-viewport -->
|
<!-- TODO: read https://www.html5rocks.com/en/mobile/mobifying/#toc-meta-viewport -->
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Emigui – A experiment in an Immediate Mode GUI written in Rust</title>
|
<title>Emigui – An experimental immediate mode GUI written in Rust</title>
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
/* Remove touch delay: */
|
/* Remove touch delay: */
|
||||||
|
@ -109,13 +109,13 @@
|
||||||
|
|
||||||
function on_wasm_loaded() {
|
function on_wasm_loaded() {
|
||||||
var canvas = document.getElementById("canvas");
|
var canvas = document.getElementById("canvas");
|
||||||
var invalidate = function() {
|
var invalidate = function () {
|
||||||
if (!ANIMATION_FRAME) {
|
if (!ANIMATION_FRAME) {
|
||||||
paint();
|
paint();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
canvas.addEventListener("mousedown", function(event) {
|
canvas.addEventListener("mousedown", function (event) {
|
||||||
if (g_is_touch) { return; }
|
if (g_is_touch) { return; }
|
||||||
g_mouse_pos = mouse_pos_from_event(canvas, event);
|
g_mouse_pos = mouse_pos_from_event(canvas, event);
|
||||||
g_mouse_down = true;
|
g_mouse_down = true;
|
||||||
|
@ -123,14 +123,14 @@
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener("mousemove", function(event) {
|
canvas.addEventListener("mousemove", function (event) {
|
||||||
if (g_is_touch) { return; }
|
if (g_is_touch) { return; }
|
||||||
g_mouse_pos = mouse_pos_from_event(canvas, event);
|
g_mouse_pos = mouse_pos_from_event(canvas, event);
|
||||||
invalidate();
|
invalidate();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener("mouseup", function(event) {
|
canvas.addEventListener("mouseup", function (event) {
|
||||||
if (g_is_touch) { return; }
|
if (g_is_touch) { return; }
|
||||||
g_mouse_pos = mouse_pos_from_event(canvas, event);
|
g_mouse_pos = mouse_pos_from_event(canvas, event);
|
||||||
g_mouse_down = false;
|
g_mouse_down = false;
|
||||||
|
@ -138,7 +138,7 @@
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener("mouseleave", function(event) {
|
canvas.addEventListener("mouseleave", function (event) {
|
||||||
if (g_is_touch) { return; }
|
if (g_is_touch) { return; }
|
||||||
g_mouse_pos = null;
|
g_mouse_pos = null;
|
||||||
invalidate();
|
invalidate();
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
canvas.addEventListener("touchstart", function(event) {
|
canvas.addEventListener("touchstart", function (event) {
|
||||||
g_is_touch = true;
|
g_is_touch = true;
|
||||||
g_mouse_pos = { x: event.touches[0].pageX, y: event.touches[0].pageY };
|
g_mouse_pos = { x: event.touches[0].pageX, y: event.touches[0].pageY };
|
||||||
g_mouse_down = true;
|
g_mouse_down = true;
|
||||||
|
@ -154,14 +154,14 @@
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener("touchmove", function(event) {
|
canvas.addEventListener("touchmove", function (event) {
|
||||||
g_is_touch = true;
|
g_is_touch = true;
|
||||||
g_mouse_pos = { x: event.touches[0].pageX, y: event.touches[0].pageY };
|
g_mouse_pos = { x: event.touches[0].pageX, y: event.touches[0].pageY };
|
||||||
invalidate();
|
invalidate();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener("touchend", function(event) {
|
canvas.addEventListener("touchend", function (event) {
|
||||||
g_is_touch = true;
|
g_is_touch = true;
|
||||||
g_mouse_down = false; // First release mouse to click...
|
g_mouse_down = false; // First release mouse to click...
|
||||||
update_gui()();
|
update_gui()();
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::WHITE,
|
|
||||||
label, layout,
|
label, layout,
|
||||||
layout::{show_popup, Region},
|
layout::Region,
|
||||||
math::{clamp, remap_clamp, vec2},
|
mesher::Mesher,
|
||||||
mesher::{Mesher, Vertex},
|
|
||||||
style::Style,
|
|
||||||
types::{GuiInput, PaintCmd},
|
types::{GuiInput, PaintCmd},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
FontDefinitions, Fonts, Mesh, RawInput, Texture,
|
FontDefinitions, Fonts, Mesh, RawInput, Texture,
|
||||||
|
@ -18,98 +15,6 @@ struct Stats {
|
||||||
num_triangles: usize,
|
num_triangles: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_style(style: &mut Style, gui: &mut Region) {
|
|
||||||
if gui.add(Button::new("Reset style")).clicked {
|
|
||||||
*style = Default::default();
|
|
||||||
}
|
|
||||||
gui.add(Slider::f32(&mut style.item_spacing.x, 0.0, 10.0).text("item_spacing.x"));
|
|
||||||
gui.add(Slider::f32(&mut style.item_spacing.y, 0.0, 10.0).text("item_spacing.y"));
|
|
||||||
gui.add(Slider::f32(&mut style.window_padding.x, 0.0, 10.0).text("window_padding.x"));
|
|
||||||
gui.add(Slider::f32(&mut style.window_padding.y, 0.0, 10.0).text("window_padding.y"));
|
|
||||||
gui.add(Slider::f32(&mut style.indent, 0.0, 100.0).text("indent"));
|
|
||||||
gui.add(Slider::f32(&mut style.button_padding.x, 0.0, 20.0).text("button_padding.x"));
|
|
||||||
gui.add(Slider::f32(&mut style.button_padding.y, 0.0, 20.0).text("button_padding.y"));
|
|
||||||
gui.add(Slider::f32(&mut style.clickable_diameter, 0.0, 60.0).text("clickable_diameter"));
|
|
||||||
gui.add(Slider::f32(&mut style.start_icon_width, 0.0, 60.0).text("start_icon_width"));
|
|
||||||
gui.add(Slider::f32(&mut style.line_width, 0.0, 10.0).text("line_width"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_font_definitions(font_definitions: &mut FontDefinitions, gui: &mut Region) {
|
|
||||||
for (text_style, (_family, size)) in font_definitions {
|
|
||||||
// TODO: radiobutton for family
|
|
||||||
gui.add(Slider::f32(size, 4.0, 40.0).text(format!("{:?}", text_style)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_font_texture(texture: &Texture, gui: &mut Region) {
|
|
||||||
gui.add(label!(
|
|
||||||
"Font texture size: {} x {} (hover to zoom)",
|
|
||||||
texture.width,
|
|
||||||
texture.height
|
|
||||||
));
|
|
||||||
let mut size = vec2(texture.width as f32, texture.height as f32);
|
|
||||||
if size.x > gui.width() {
|
|
||||||
size *= gui.width() / size.x;
|
|
||||||
}
|
|
||||||
let interact = gui.reserve_space(size, None);
|
|
||||||
let rect = interact.rect;
|
|
||||||
let top_left = Vertex {
|
|
||||||
pos: rect.min(),
|
|
||||||
uv: (0, 0),
|
|
||||||
color: WHITE,
|
|
||||||
};
|
|
||||||
let bottom_right = Vertex {
|
|
||||||
pos: rect.max(),
|
|
||||||
uv: (texture.width as u16 - 1, texture.height as u16 - 1),
|
|
||||||
color: WHITE,
|
|
||||||
};
|
|
||||||
let mut mesh = Mesh::default();
|
|
||||||
mesh.add_rect(top_left, bottom_right);
|
|
||||||
gui.add_paint_cmd(PaintCmd::Mesh(mesh));
|
|
||||||
|
|
||||||
if let Some(mouse_pos) = gui.input().mouse_pos {
|
|
||||||
if interact.hovered {
|
|
||||||
show_popup(gui.data(), mouse_pos, |gui| {
|
|
||||||
let zoom_rect = gui.reserve_space(vec2(128.0, 128.0), None).rect;
|
|
||||||
let u = remap_clamp(
|
|
||||||
mouse_pos.x,
|
|
||||||
rect.min().x,
|
|
||||||
rect.max().x,
|
|
||||||
0.0,
|
|
||||||
texture.width as f32 - 1.0,
|
|
||||||
)
|
|
||||||
.round();
|
|
||||||
let v = remap_clamp(
|
|
||||||
mouse_pos.y,
|
|
||||||
rect.min().y,
|
|
||||||
rect.max().y,
|
|
||||||
0.0,
|
|
||||||
texture.height as f32 - 1.0,
|
|
||||||
)
|
|
||||||
.round();
|
|
||||||
|
|
||||||
let texel_radius = 32.0;
|
|
||||||
let u = clamp(u, texel_radius, texture.width as f32 - 1.0 - texel_radius);
|
|
||||||
let v = clamp(v, texel_radius, texture.height as f32 - 1.0 - texel_radius);
|
|
||||||
|
|
||||||
let top_left = Vertex {
|
|
||||||
pos: zoom_rect.min(),
|
|
||||||
uv: ((u - texel_radius) as u16, (v - texel_radius) as u16),
|
|
||||||
color: WHITE,
|
|
||||||
};
|
|
||||||
let bottom_right = Vertex {
|
|
||||||
pos: zoom_rect.max(),
|
|
||||||
uv: ((u + texel_radius) as u16, (v + texel_radius) as u16),
|
|
||||||
color: WHITE,
|
|
||||||
};
|
|
||||||
let mut mesh = Mesh::default();
|
|
||||||
mesh.add_rect(top_left, bottom_right);
|
|
||||||
gui.add_paint_cmd(PaintCmd::Mesh(mesh));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encapsulates input, layout and painting for ease of use.
|
/// Encapsulates input, layout and painting for ease of use.
|
||||||
pub struct Emigui {
|
pub struct Emigui {
|
||||||
pub last_input: RawInput,
|
pub last_input: RawInput,
|
||||||
|
@ -164,18 +69,16 @@ impl Emigui {
|
||||||
mesh
|
mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn example(&mut self, region: &mut Region) {
|
pub fn ui(&mut self, region: &mut Region) {
|
||||||
region.foldable("Style", |gui| {
|
region.foldable("Style", |region| {
|
||||||
let mut style = self.data.style();
|
self.data.style_ui(region);
|
||||||
show_style(&mut style, gui);
|
|
||||||
self.data.set_style(style);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
region.foldable("Fonts", |gui| {
|
region.foldable("Fonts", |region| {
|
||||||
let old_font_definitions = self.data.fonts.definitions();
|
let old_font_definitions = self.data.fonts.definitions();
|
||||||
let mut new_font_definitions = old_font_definitions.clone();
|
let mut new_font_definitions = old_font_definitions.clone();
|
||||||
show_font_definitions(&mut new_font_definitions, gui);
|
font_definitions_ui(&mut new_font_definitions, region);
|
||||||
show_font_texture(self.texture(), gui);
|
self.data.fonts.texture().ui(region);
|
||||||
if *old_font_definitions != new_font_definitions {
|
if *old_font_definitions != new_font_definitions {
|
||||||
let mut new_data = (*self.data).clone();
|
let mut new_data = (*self.data).clone();
|
||||||
let fonts =
|
let fonts =
|
||||||
|
@ -185,25 +88,39 @@ impl Emigui {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
region.foldable("Stats", |gui| {
|
region.foldable("Stats", |region| {
|
||||||
gui.add(label!(
|
region.add(label!(
|
||||||
"Screen size: {} x {} points, pixels_per_point: {}",
|
"Screen size: {} x {} points, pixels_per_point: {}",
|
||||||
gui.input().screen_size.x,
|
region.input().screen_size.x,
|
||||||
gui.input().screen_size.y,
|
region.input().screen_size.y,
|
||||||
gui.input().pixels_per_point,
|
region.input().pixels_per_point,
|
||||||
));
|
));
|
||||||
if let Some(mouse_pos) = gui.input().mouse_pos {
|
if let Some(mouse_pos) = region.input().mouse_pos {
|
||||||
gui.add(label!("mouse_pos: {} x {}", mouse_pos.x, mouse_pos.y,));
|
region.add(label!("mouse_pos: {} x {}", mouse_pos.x, mouse_pos.y,));
|
||||||
} else {
|
} else {
|
||||||
gui.add(label!("mouse_pos: None"));
|
region.add(label!("mouse_pos: None"));
|
||||||
}
|
}
|
||||||
gui.add(label!(
|
region.add(label!(
|
||||||
"gui cursor: {} x {}",
|
"region cursor: {} x {}",
|
||||||
gui.cursor().x,
|
region.cursor().x,
|
||||||
gui.cursor().y,
|
region.cursor().y,
|
||||||
));
|
));
|
||||||
gui.add(label!("num_vertices: {}", self.stats.num_vertices));
|
region.add(label!("num_vertices: {}", self.stats.num_vertices));
|
||||||
gui.add(label!("num_triangles: {}", self.stats.num_triangles));
|
region.add(label!("num_triangles: {}", self.stats.num_triangles));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn font_definitions_ui(font_definitions: &mut FontDefinitions, region: &mut Region) {
|
||||||
|
for (text_style, (_family, size)) in font_definitions.iter_mut() {
|
||||||
|
// TODO: radiobutton for family
|
||||||
|
region.add(
|
||||||
|
Slider::f32(size, 4.0, 40.0)
|
||||||
|
.precision(0)
|
||||||
|
.text(format!("{:?}", text_style)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if region.add(Button::new("Reset fonts")).clicked {
|
||||||
|
*font_definitions = crate::fonts::default_font_definitions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
use emigui::{color::*, label, math::*, widgets::*, Align, Outline, PaintCmd, Region, TextStyle};
|
use crate::{color::*, label, math::*, widgets::*, Align, Outline, PaintCmd, Region};
|
||||||
|
|
||||||
pub fn show_value_gui(value: &mut usize, gui: &mut Region) {
|
/// Showcase some region code
|
||||||
gui.add(Slider::usize(value, 1, 1000));
|
pub struct ExampleApp {
|
||||||
if gui.add(Button::new("Double it")).clicked {
|
|
||||||
*value *= 2;
|
|
||||||
}
|
|
||||||
gui.add(label!("Value: {}", value));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
checked: bool,
|
checked: bool,
|
||||||
count: usize,
|
count: usize,
|
||||||
radio: usize,
|
radio: usize,
|
||||||
|
@ -23,9 +16,9 @@ pub struct App {
|
||||||
slider_value: usize,
|
slider_value: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl Default for ExampleApp {
|
||||||
fn default() -> App {
|
fn default() -> ExampleApp {
|
||||||
App {
|
ExampleApp {
|
||||||
checked: true,
|
checked: true,
|
||||||
radio: 0,
|
radio: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -41,53 +34,53 @@ impl Default for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl ExampleApp {
|
||||||
pub fn show_gui(&mut self, gui: &mut Region) {
|
pub fn ui(&mut self, region: &mut Region) {
|
||||||
gui.add(label!("Emigui!").text_style(TextStyle::Heading));
|
region.foldable("About Emigui", |region| {
|
||||||
gui.add(label!("Emigui is an Immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."));
|
region.add(label!("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."));
|
||||||
gui.add(Separator::new());
|
});
|
||||||
|
|
||||||
gui.foldable("Widget examples", |gui| {
|
region.foldable("Widget examples", |region| {
|
||||||
gui.horizontal(Align::Min, |gui| {
|
region.horizontal(Align::Min, |region| {
|
||||||
gui.add(label!("Text can have").text_color(srgba(110, 255, 110, 255)));
|
region.add(label!("Text can have").text_color(srgba(110, 255, 110, 255)));
|
||||||
gui.add(label!("color").text_color(srgba(128, 140, 255, 255)));
|
region.add(label!("color").text_color(srgba(128, 140, 255, 255)));
|
||||||
gui.add(label!("and tooltips (hover me)")).tooltip_text(
|
region.add(label!("and tooltips (hover me)")).tooltip_text(
|
||||||
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
|
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
gui.add(Checkbox::new(&mut self.checked, "checkbox"));
|
region.add(Checkbox::new(&mut self.checked, "checkbox"));
|
||||||
|
|
||||||
gui.horizontal(Align::Min, |gui| {
|
region.horizontal(Align::Min, |region| {
|
||||||
if gui.add(radio(self.radio == 0, "First")).clicked {
|
if region.add(radio(self.radio == 0, "First")).clicked {
|
||||||
self.radio = 0;
|
self.radio = 0;
|
||||||
}
|
}
|
||||||
if gui.add(radio(self.radio == 1, "Second")).clicked {
|
if region.add(radio(self.radio == 1, "Second")).clicked {
|
||||||
self.radio = 1;
|
self.radio = 1;
|
||||||
}
|
}
|
||||||
if gui.add(radio(self.radio == 2, "Final")).clicked {
|
if region.add(radio(self.radio == 2, "Final")).clicked {
|
||||||
self.radio = 2;
|
self.radio = 2;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gui.horizontal(Align::Min, |gui| {
|
region.horizontal(Align::Min, |region| {
|
||||||
if gui
|
if region
|
||||||
.add(Button::new("Click me"))
|
.add(Button::new("Click me"))
|
||||||
.tooltip_text("This will just increase a counter.")
|
.tooltip_text("This will just increase a counter.")
|
||||||
.clicked
|
.clicked
|
||||||
{
|
{
|
||||||
self.count += 1;
|
self.count += 1;
|
||||||
}
|
}
|
||||||
gui.add(label!(
|
region.add(label!(
|
||||||
"The button have been clicked {} times",
|
"The button have been clicked {} times",
|
||||||
self.count
|
self.count
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gui.foldable("Layouts", |gui| {
|
region.foldable("Layouts", |region| {
|
||||||
gui.add(Slider::usize(&mut self.num_columns, 1, 10).text("Columns"));
|
region.add(Slider::usize(&mut self.num_columns, 1, 10).text("Columns"));
|
||||||
gui.columns(self.num_columns, |cols| {
|
region.columns(self.num_columns, |cols| {
|
||||||
for (i, col) in cols.iter_mut().enumerate() {
|
for (i, col) in cols.iter_mut().enumerate() {
|
||||||
col.add(label!("Column {} out of {}", i + 1, self.num_columns));
|
col.add(label!("Column {} out of {}", i + 1, self.num_columns));
|
||||||
if i + 1 == self.num_columns {
|
if i + 1 == self.num_columns {
|
||||||
|
@ -99,14 +92,14 @@ impl App {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gui.foldable("Test box rendering", |gui| {
|
region.foldable("Test box rendering", |region| {
|
||||||
gui.add(Slider::f32(&mut self.size.x, 0.0, 500.0).text("width"));
|
region.add(Slider::f32(&mut self.size.x, 0.0, 500.0).text("width"));
|
||||||
gui.add(Slider::f32(&mut self.size.y, 0.0, 500.0).text("height"));
|
region.add(Slider::f32(&mut self.size.y, 0.0, 500.0).text("height"));
|
||||||
gui.add(Slider::f32(&mut self.corner_radius, 0.0, 50.0).text("corner_radius"));
|
region.add(Slider::f32(&mut self.corner_radius, 0.0, 50.0).text("corner_radius"));
|
||||||
gui.add(Slider::f32(&mut self.stroke_width, 0.0, 10.0).text("stroke_width"));
|
region.add(Slider::f32(&mut self.stroke_width, 0.0, 10.0).text("stroke_width"));
|
||||||
gui.add(Slider::usize(&mut self.num_boxes, 0, 5).text("num_boxes"));
|
region.add(Slider::usize(&mut self.num_boxes, 0, 5).text("num_boxes"));
|
||||||
|
|
||||||
let pos = gui
|
let pos = region
|
||||||
.reserve_space(
|
.reserve_space(
|
||||||
vec2(self.size.x * (self.num_boxes as f32), self.size.y),
|
vec2(self.size.x * (self.num_boxes as f32), self.size.y),
|
||||||
None,
|
None,
|
||||||
|
@ -129,11 +122,19 @@ impl App {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
gui.add_paint_cmds(cmds);
|
region.add_paint_cmds(cmds);
|
||||||
});
|
});
|
||||||
|
|
||||||
gui.foldable("Slider example", |gui| {
|
region.foldable("Slider example", |region| {
|
||||||
show_value_gui(&mut self.slider_value, gui);
|
value_ui(&mut self.slider_value, region);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn value_ui(value: &mut usize, region: &mut Region) {
|
||||||
|
region.add(Slider::usize(value, 1, 1000));
|
||||||
|
if region.add(Button::new("Double it")).clicked {
|
||||||
|
*value *= 2;
|
||||||
|
}
|
||||||
|
region.add(label!("Value: {}", value));
|
||||||
|
}
|
|
@ -26,6 +26,15 @@ pub enum FontFamily {
|
||||||
|
|
||||||
pub type FontDefinitions = BTreeMap<TextStyle, (FontFamily, f32)>;
|
pub type FontDefinitions = BTreeMap<TextStyle, (FontFamily, f32)>;
|
||||||
|
|
||||||
|
pub fn default_font_definitions() -> FontDefinitions {
|
||||||
|
let mut definitions = FontDefinitions::new();
|
||||||
|
definitions.insert(TextStyle::Body, (FontFamily::VariableWidth, 16.0));
|
||||||
|
definitions.insert(TextStyle::Button, (FontFamily::VariableWidth, 18.0));
|
||||||
|
definitions.insert(TextStyle::Heading, (FontFamily::VariableWidth, 28.0));
|
||||||
|
definitions.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0));
|
||||||
|
definitions
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Fonts {
|
pub struct Fonts {
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
definitions: FontDefinitions,
|
definitions: FontDefinitions,
|
||||||
|
@ -35,12 +44,7 @@ pub struct Fonts {
|
||||||
|
|
||||||
impl Fonts {
|
impl Fonts {
|
||||||
pub fn new(pixels_per_point: f32) -> Fonts {
|
pub fn new(pixels_per_point: f32) -> Fonts {
|
||||||
let mut definitions = FontDefinitions::new();
|
Fonts::from_definitions(default_font_definitions(), pixels_per_point)
|
||||||
definitions.insert(TextStyle::Body, (FontFamily::VariableWidth, 16.0));
|
|
||||||
definitions.insert(TextStyle::Button, (FontFamily::VariableWidth, 18.0));
|
|
||||||
definitions.insert(TextStyle::Heading, (FontFamily::VariableWidth, 28.0));
|
|
||||||
definitions.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0));
|
|
||||||
Fonts::from_definitions(definitions, pixels_per_point)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_definitions(definitions: FontDefinitions, pixels_per_point: f32) -> Fonts {
|
pub fn from_definitions(definitions: FontDefinitions, pixels_per_point: f32) -> Fonts {
|
||||||
|
|
|
@ -192,6 +192,14 @@ impl Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
pub fn style_ui(&self, region: &mut Region) {
|
||||||
|
let mut style = self.style();
|
||||||
|
style.ui(region);
|
||||||
|
self.set_style(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Show a pop-over window
|
/// Show a pop-over window
|
||||||
pub fn show_popup<F>(data: &Arc<Data>, window_pos: Vec2, add_contents: F)
|
pub fn show_popup<F>(data: &Arc<Data>, window_pos: Vec2, add_contents: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -8,6 +8,7 @@ extern crate serde_derive;
|
||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
mod emigui;
|
mod emigui;
|
||||||
|
pub mod example_app;
|
||||||
mod font;
|
mod font;
|
||||||
mod fonts;
|
mod fonts;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
|
|
@ -86,3 +86,22 @@ impl Style {
|
||||||
(small_icon_rect, big_icon_rect)
|
(small_icon_rect, big_icon_rect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Style {
|
||||||
|
pub fn ui(&mut self, region: &mut crate::Region) {
|
||||||
|
use crate::widgets::{Button, Slider};
|
||||||
|
if region.add(Button::new("Reset style")).clicked {
|
||||||
|
*self = Default::default();
|
||||||
|
}
|
||||||
|
region.add(Slider::f32(&mut self.item_spacing.x, 0.0, 10.0).text("item_spacing.x"));
|
||||||
|
region.add(Slider::f32(&mut self.item_spacing.y, 0.0, 10.0).text("item_spacing.y"));
|
||||||
|
region.add(Slider::f32(&mut self.window_padding.x, 0.0, 10.0).text("window_padding.x"));
|
||||||
|
region.add(Slider::f32(&mut self.window_padding.y, 0.0, 10.0).text("window_padding.y"));
|
||||||
|
region.add(Slider::f32(&mut self.indent, 0.0, 100.0).text("indent"));
|
||||||
|
region.add(Slider::f32(&mut self.button_padding.x, 0.0, 20.0).text("button_padding.x"));
|
||||||
|
region.add(Slider::f32(&mut self.button_padding.y, 0.0, 20.0).text("button_padding.y"));
|
||||||
|
region.add(Slider::f32(&mut self.clickable_diameter, 0.0, 60.0).text("clickable_diameter"));
|
||||||
|
region.add(Slider::f32(&mut self.start_icon_width, 0.0, 60.0).text("start_icon_width"));
|
||||||
|
region.add(Slider::f32(&mut self.line_width, 0.0, 10.0).text("line_width"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -89,3 +89,79 @@ impl TextureAtlas {
|
||||||
(pos.0 as usize, pos.1 as usize)
|
(pos.0 as usize, pos.1 as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Texture {
|
||||||
|
pub fn ui(&self, region: &mut crate::Region) {
|
||||||
|
use crate::{
|
||||||
|
color::WHITE, label, layout::show_popup, math::*, widgets::Label, Mesh, PaintCmd,
|
||||||
|
Vertex,
|
||||||
|
};
|
||||||
|
|
||||||
|
region.add(label!(
|
||||||
|
"Texture size: {} x {} (hover to zoom)",
|
||||||
|
self.width,
|
||||||
|
self.height
|
||||||
|
));
|
||||||
|
let mut size = vec2(self.width as f32, self.height as f32);
|
||||||
|
if size.x > region.width() {
|
||||||
|
size *= region.width() / size.x;
|
||||||
|
}
|
||||||
|
let interact = region.reserve_space(size, None);
|
||||||
|
let rect = interact.rect;
|
||||||
|
let top_left = Vertex {
|
||||||
|
pos: rect.min(),
|
||||||
|
uv: (0, 0),
|
||||||
|
color: WHITE,
|
||||||
|
};
|
||||||
|
let bottom_right = Vertex {
|
||||||
|
pos: rect.max(),
|
||||||
|
uv: (self.width as u16 - 1, self.height as u16 - 1),
|
||||||
|
color: WHITE,
|
||||||
|
};
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
mesh.add_rect(top_left, bottom_right);
|
||||||
|
region.add_paint_cmd(PaintCmd::Mesh(mesh));
|
||||||
|
|
||||||
|
if let Some(mouse_pos) = region.input().mouse_pos {
|
||||||
|
if interact.hovered {
|
||||||
|
show_popup(region.data(), mouse_pos, |region| {
|
||||||
|
let zoom_rect = region.reserve_space(vec2(128.0, 128.0), None).rect;
|
||||||
|
let u = remap_clamp(
|
||||||
|
mouse_pos.x,
|
||||||
|
rect.min().x,
|
||||||
|
rect.max().x,
|
||||||
|
0.0,
|
||||||
|
self.width as f32 - 1.0,
|
||||||
|
)
|
||||||
|
.round();
|
||||||
|
let v = remap_clamp(
|
||||||
|
mouse_pos.y,
|
||||||
|
rect.min().y,
|
||||||
|
rect.max().y,
|
||||||
|
0.0,
|
||||||
|
self.height as f32 - 1.0,
|
||||||
|
)
|
||||||
|
.round();
|
||||||
|
|
||||||
|
let texel_radius = 32.0;
|
||||||
|
let u = clamp(u, texel_radius, self.width as f32 - 1.0 - texel_radius);
|
||||||
|
let v = clamp(v, texel_radius, self.height as f32 - 1.0 - texel_radius);
|
||||||
|
|
||||||
|
let top_left = Vertex {
|
||||||
|
pos: zoom_rect.min(),
|
||||||
|
uv: ((u - texel_radius) as u16, (v - texel_radius) as u16),
|
||||||
|
color: WHITE,
|
||||||
|
};
|
||||||
|
let bottom_right = Vertex {
|
||||||
|
pos: zoom_rect.max(),
|
||||||
|
uv: ((u + texel_radius) as u16, (v + texel_radius) as u16),
|
||||||
|
color: WHITE,
|
||||||
|
};
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
mesh.add_rect(top_left, bottom_right);
|
||||||
|
region.add_paint_cmd(PaintCmd::Mesh(mesh));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub trait Widget {
|
||||||
|
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
text: String,
|
text: String,
|
||||||
text_style: TextStyle,
|
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the region"
|
||||||
text_color: Option<Color>,
|
text_color: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +331,17 @@ impl<'a> Slider<'a> {
|
||||||
self.precision = precision;
|
self.precision = precision;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_value_f32(&mut self) -> f32 {
|
||||||
|
(self.get_set_value)(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_value_f32(&mut self, mut value: f32) {
|
||||||
|
if self.precision == 0 {
|
||||||
|
value = value.round();
|
||||||
|
}
|
||||||
|
(self.get_set_value)(Some(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Slider<'a> {
|
impl<'a> Widget for Slider<'a> {
|
||||||
|
@ -341,12 +352,8 @@ impl<'a> Widget for Slider<'a> {
|
||||||
if let Some(text) = &self.text {
|
if let Some(text) = &self.text {
|
||||||
let text_on_top = self.text_on_top.unwrap_or_default();
|
let text_on_top = self.text_on_top.unwrap_or_default();
|
||||||
let text_color = self.text_color;
|
let text_color = self.text_color;
|
||||||
let full_text = format!(
|
let value = (self.get_set_value)(None);
|
||||||
"{}: {:.*}",
|
let full_text = format!("{}: {:.*}", text, self.precision, value);
|
||||||
text,
|
|
||||||
self.precision,
|
|
||||||
(self.get_set_value)(None)
|
|
||||||
);
|
|
||||||
let id = Some(self.id.unwrap_or_else(|| make_id(text)));
|
let id = Some(self.id.unwrap_or_else(|| make_id(text)));
|
||||||
let mut naked = self;
|
let mut naked = self;
|
||||||
naked.id = id;
|
naked.id = id;
|
||||||
|
@ -386,19 +393,19 @@ impl<'a> Widget for Slider<'a> {
|
||||||
|
|
||||||
if let Some(mouse_pos) = region.input().mouse_pos {
|
if let Some(mouse_pos) = region.input().mouse_pos {
|
||||||
if interact.active {
|
if interact.active {
|
||||||
(self.get_set_value)(Some(remap_clamp(
|
self.set_value_f32(remap_clamp(
|
||||||
mouse_pos.x,
|
mouse_pos.x,
|
||||||
interact.rect.min().x,
|
interact.rect.min().x,
|
||||||
interact.rect.max().x,
|
interact.rect.max().x,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint it:
|
// Paint it:
|
||||||
{
|
{
|
||||||
let value = (self.get_set_value)(None);
|
let value = self.get_value_f32();
|
||||||
|
|
||||||
let rect = interact.rect;
|
let rect = interact.rect;
|
||||||
let thickness = rect.size().y;
|
let thickness = rect.size().y;
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
emigui::{
|
emigui::{
|
||||||
|
example_app::ExampleApp,
|
||||||
label,
|
label,
|
||||||
math::vec2,
|
math::vec2,
|
||||||
widgets::{Button, Label},
|
widgets::{Button, Label},
|
||||||
|
@ -37,6 +38,8 @@ fn main() {
|
||||||
|
|
||||||
let mut frame_start = Instant::now();
|
let mut frame_start = Instant::now();
|
||||||
|
|
||||||
|
let mut example_app = ExampleApp::default();
|
||||||
|
|
||||||
while !quit {
|
while !quit {
|
||||||
{
|
{
|
||||||
// Keep smooth frame rate. TODO: proper vsync
|
// Keep smooth frame rate. TODO: proper vsync
|
||||||
|
@ -85,7 +88,8 @@ fn main() {
|
||||||
if region.add(Button::new("Quit")).clicked {
|
if region.add(Button::new("Quit")).clicked {
|
||||||
quit = true;
|
quit = true;
|
||||||
}
|
}
|
||||||
emigui.example(&mut region);
|
example_app.ui(&mut region);
|
||||||
|
emigui.ui(&mut region);
|
||||||
let mesh = emigui.paint();
|
let mesh = emigui.paint();
|
||||||
painter.paint(&display, mesh, emigui.texture());
|
painter.paint(&display, mesh, emigui.texture());
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,21 @@ extern crate emigui;
|
||||||
extern crate emigui_wasm;
|
extern crate emigui_wasm;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
emigui::{color::srgba, label, widgets::Label, Emigui, RawInput},
|
emigui::{
|
||||||
|
color::srgba,
|
||||||
|
example_app::ExampleApp,
|
||||||
|
label,
|
||||||
|
widgets::{Label, Separator},
|
||||||
|
Align, Emigui, RawInput, TextStyle,
|
||||||
|
},
|
||||||
emigui_wasm::now_sec,
|
emigui_wasm::now_sec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
mod app;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
app: app::App,
|
example_app: ExampleApp,
|
||||||
emigui: Emigui,
|
emigui: Emigui,
|
||||||
webgl_painter: emigui_wasm::webgl::Painter,
|
webgl_painter: emigui_wasm::webgl::Painter,
|
||||||
everything_ms: f64,
|
everything_ms: f64,
|
||||||
|
@ -26,7 +30,7 @@ pub struct State {
|
||||||
impl State {
|
impl State {
|
||||||
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
|
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
|
||||||
Ok(State {
|
Ok(State {
|
||||||
app: Default::default(),
|
example_app: Default::default(),
|
||||||
emigui: Emigui::new(pixels_per_point),
|
emigui: Emigui::new(pixels_per_point),
|
||||||
webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?,
|
webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?,
|
||||||
everything_ms: 0.0,
|
everything_ms: 0.0,
|
||||||
|
@ -40,15 +44,23 @@ impl State {
|
||||||
|
|
||||||
let mut region = self.emigui.whole_screen_region();
|
let mut region = self.emigui.whole_screen_region();
|
||||||
let mut region = region.centered_column(region.width().min(480.0));
|
let mut region = region.centered_column(region.width().min(480.0));
|
||||||
self.app.show_gui(&mut region);
|
region.add(label!("Emigui!").text_style(TextStyle::Heading));
|
||||||
self.emigui.example(&mut region);
|
region.add(label!("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."));
|
||||||
|
region.add(label!(
|
||||||
|
"Everything you see is rendered as textured triangles. There is no DOM. There are not HTML elements."
|
||||||
|
));
|
||||||
|
region.add(Separator::new());
|
||||||
|
self.example_app.ui(&mut region);
|
||||||
|
self.emigui.ui(&mut region);
|
||||||
|
|
||||||
|
region.set_align(Align::Min);
|
||||||
region.add(label!("WebGl painter info:"));
|
region.add(label!("WebGl painter info:"));
|
||||||
region.indent(|region| {
|
region.indent(|region| {
|
||||||
region.add(label!(self.webgl_painter.debug_info()));
|
region.add(label!(self.webgl_painter.debug_info()));
|
||||||
});
|
});
|
||||||
|
region.add(
|
||||||
region.add(label!("Everything: {:.1} ms", self.everything_ms));
|
label!("Everything: {:.1} ms", self.everything_ms).text_style(TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
|
||||||
let bg_color = srgba(16, 16, 16, 255);
|
let bg_color = srgba(16, 16, 16, 255);
|
||||||
let mesh = self.emigui.paint();
|
let mesh = self.emigui.paint();
|
||||||
|
|
6
start_server.sh
Executable file
6
start_server.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
cd docs
|
||||||
|
echo "open localhost:8000"
|
||||||
|
python3 -m http.server 8000 --bind 127.0.0.1
|
Loading…
Reference in a new issue