Store/restore emigui memory state (window positions, sizes etc)
This commit is contained in:
parent
d52cccde7b
commit
bfbb669d02
19 changed files with 154 additions and 60 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -192,6 +192,7 @@ dependencies = [
|
||||||
"clipboard 0.5.0 (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",
|
"emigui 0.1.0",
|
||||||
"glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,15 @@ Mostly a tech demo at this point. I hope to find time to work more on this in th
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
* Text
|
* Labels
|
||||||
* Buttons, checkboxes, radio buttons and sliders
|
* Buttons, checkboxes, radio buttons and sliders
|
||||||
* Horizontal or vertical layout
|
* Horizontal or vertical layout
|
||||||
* Column layout
|
* Column layout
|
||||||
* Collapsible headers (sections)
|
* Collapsible headers (sections)
|
||||||
|
* Windows
|
||||||
|
* Resizable regions
|
||||||
|
* Vertical scolling
|
||||||
|
* Simple text input
|
||||||
* Anti-aliased rendering of circles, rounded rectangles and lines.
|
* Anti-aliased rendering of circles, rounded rectangles and lines.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ This is the core library crate Emigui. It is fully platform independent without
|
||||||
### Web version:
|
### Web version:
|
||||||
* [x] Scroll input
|
* [x] Scroll input
|
||||||
* [x] Change to resize cursor on hover
|
* [x] Change to resize cursor on hover
|
||||||
|
* [ ] Make it a JS library for easily creating your own stuff
|
||||||
|
|
||||||
### Animations
|
### Animations
|
||||||
Add extremely quick animations for some things, maybe 2-3 frames. For instance:
|
Add extremely quick animations for some things, maybe 2-3 frames. For instance:
|
||||||
|
@ -63,10 +64,15 @@ Add extremely quick animations for some things, maybe 2-3 frames. For instance:
|
||||||
### Input
|
### Input
|
||||||
* [ ] Distinguish between clicks and drags
|
* [ ] Distinguish between clicks and drags
|
||||||
* [ ] Double-click
|
* [ ] Double-click
|
||||||
* [ ] Text
|
* [x] Text
|
||||||
|
|
||||||
|
### Debugability / Inspection
|
||||||
|
* [x] Widget debug rectangles
|
||||||
|
* [ ] Easily debug why something keeps expanding
|
||||||
|
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
* [ ] Persist UI state in external storage
|
* [x] Persist UI state in external storage
|
||||||
* [ ] Pixel-perfect rendering (round positions to nearest pixel).
|
* [ ] Pixel-perfect rendering (round positions to nearest pixel).
|
||||||
* [ ] Build in a profiler which tracks which region in which window takes up CPU.
|
* [ ] Build in a profiler which tracks which region in which window takes up CPU.
|
||||||
* [ ] Draw as flame graph
|
* [ ] Draw as flame graph
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use crate::{layout::Direction, *};
|
use crate::{layout::Direction, *};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
pub open: bool,
|
open: bool,
|
||||||
pub toggle_time: f64,
|
#[serde(skip)] // Times are relative, and we don't want to continue animations anyway
|
||||||
|
toggle_time: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
open: false,
|
open: false,
|
||||||
toggle_time: -std::f64::INFINITY,
|
toggle_time: -f64::INFINITY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +61,7 @@ impl CollapsingHeader {
|
||||||
);
|
);
|
||||||
|
|
||||||
let state = {
|
let state = {
|
||||||
let mut memory = region.ctx.memory.lock();
|
let mut memory = region.ctx.memory();
|
||||||
let mut state = memory.collapsing_headers.entry(id).or_insert(State {
|
let mut state = memory.collapsing_headers.entry(id).or_insert(State {
|
||||||
open: default_open,
|
open: default_open,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -7,8 +7,8 @@ use std::{fmt::Debug, hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub struct State {
|
pub(crate) struct State {
|
||||||
/// Last known pos
|
/// Last known pos
|
||||||
pub pos: Pos2,
|
pub pos: Pos2,
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ impl Floating {
|
||||||
let id = ctx.register_unique_id(self.id, "Floating", default_pos);
|
let id = ctx.register_unique_id(self.id, "Floating", default_pos);
|
||||||
let layer = Layer::Window(id);
|
let layer = Layer::Window(id);
|
||||||
|
|
||||||
let (mut state, _is_new) = match ctx.memory.lock().get_floating(id) {
|
let (mut state, _is_new) = match ctx.memory().get_floating(id) {
|
||||||
Some(state) => (state, false),
|
Some(state) => (state, false),
|
||||||
None => {
|
None => {
|
||||||
let state = State {
|
let state = State {
|
||||||
|
@ -90,15 +90,15 @@ impl Floating {
|
||||||
state.pos = state.pos.round();
|
state.pos = state.pos.round();
|
||||||
|
|
||||||
if move_interact.active || mouse_pressed_on_floating(ctx, id) {
|
if move_interact.active || mouse_pressed_on_floating(ctx, id) {
|
||||||
ctx.memory.lock().move_floating_to_top(id);
|
ctx.memory().move_floating_to_top(id);
|
||||||
}
|
}
|
||||||
ctx.memory.lock().set_floating_state(id, state);
|
ctx.memory().set_floating_state(id, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_pressed_on_floating(ctx: &Context, id: Id) -> bool {
|
fn mouse_pressed_on_floating(ctx: &Context, id: Id) -> bool {
|
||||||
if let Some(mouse_pos) = ctx.input.mouse_pos {
|
if let Some(mouse_pos) = ctx.input.mouse_pos {
|
||||||
ctx.input.mouse_pressed && ctx.memory.lock().layer_at(mouse_pos) == Layer::Window(id)
|
ctx.input.mouse_pressed && ctx.memory().layer_at(mouse_pos) == Layer::Window(id)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#![allow(unused_variables)] // TODO
|
#![allow(unused_variables)] // TODO
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub struct State {
|
pub(crate) struct State {
|
||||||
pub size: Vec2,
|
size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: auto-shink/grow should be part of another container!
|
// TODO: auto-shink/grow should be part of another container!
|
||||||
|
@ -229,7 +229,7 @@ impl Resize {
|
||||||
paint_resize_corner(region, &corner_rect, &corner_interact);
|
paint_resize_corner(region, &corner_rect, &corner_interact);
|
||||||
|
|
||||||
if corner_interact.hovered || corner_interact.active {
|
if corner_interact.hovered || corner_interact.active {
|
||||||
region.ctx().output.lock().cursor_icon = CursorIcon::ResizeNwSe;
|
region.ctx().output().cursor_icon = CursorIcon::ResizeNwSe;
|
||||||
}
|
}
|
||||||
|
|
||||||
region.memory().resize.insert(id, state);
|
region.memory().resize.insert(id, state);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct State {
|
#[serde(default)]
|
||||||
|
pub(crate) struct State {
|
||||||
/// Positive offset means scrolling down/right
|
/// Positive offset means scrolling down/right
|
||||||
pub offset: Vec2,
|
offset: Vec2,
|
||||||
|
|
||||||
pub show_scroll: bool, // TODO: default value?
|
show_scroll: bool, // TODO: default value?
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: rename VScroll
|
// TODO: rename VScroll
|
||||||
|
@ -49,8 +50,7 @@ impl ScrollArea {
|
||||||
|
|
||||||
let scroll_area_id = outer_region.id.with("scroll_area");
|
let scroll_area_id = outer_region.id.with("scroll_area");
|
||||||
let mut state = ctx
|
let mut state = ctx
|
||||||
.memory
|
.memory()
|
||||||
.lock()
|
|
||||||
.scroll_areas
|
.scroll_areas
|
||||||
.get(&scroll_area_id)
|
.get(&scroll_area_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -105,7 +105,7 @@ impl ScrollArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check that nothing else is being inteacted with
|
// TODO: check that nothing else is being inteacted with
|
||||||
if outer_region.contains_mouse(&outer_rect) && ctx.memory.lock().active_id.is_none() {
|
if outer_region.contains_mouse(&outer_rect) && ctx.memory().active_id.is_none() {
|
||||||
state.offset.y -= ctx.input.scroll_delta.y;
|
state.offset.y -= ctx.input.scroll_delta.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +194,7 @@ impl ScrollArea {
|
||||||
state.show_scroll = show_scroll_this_frame;
|
state.show_scroll = show_scroll_this_frame;
|
||||||
|
|
||||||
outer_region
|
outer_region
|
||||||
.ctx()
|
.memory()
|
||||||
.memory
|
|
||||||
.lock()
|
|
||||||
.scroll_areas
|
.scroll_areas
|
||||||
.insert(scroll_area_id, state);
|
.insert(scroll_area_id, state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ pub struct Context {
|
||||||
/// Raw input from last frame. Use `input()` instead.
|
/// Raw input from last frame. Use `input()` instead.
|
||||||
pub(crate) last_raw_input: RawInput,
|
pub(crate) last_raw_input: RawInput,
|
||||||
pub(crate) input: GuiInput,
|
pub(crate) input: GuiInput,
|
||||||
pub(crate) memory: Mutex<Memory>,
|
memory: Mutex<Memory>,
|
||||||
pub(crate) graphics: Mutex<GraphicLayers>,
|
pub(crate) graphics: Mutex<GraphicLayers>,
|
||||||
|
|
||||||
pub output: Mutex<Output>,
|
output: Mutex<Output>,
|
||||||
|
|
||||||
/// Used to debug name clashes of e.g. windows
|
/// Used to debug name clashes of e.g. windows
|
||||||
used_ids: Mutex<HashMap<Id, Pos2>>,
|
used_ids: Mutex<HashMap<Id, Pos2>>,
|
||||||
|
@ -51,6 +51,31 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn memory(&self) -> parking_lot::MutexGuard<Memory> {
|
||||||
|
self.memory.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output(&self) -> parking_lot::MutexGuard<Output> {
|
||||||
|
self.output.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input(&self) -> &GuiInput {
|
||||||
|
&self.input
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raw input from last frame. Use `input()` instead.
|
||||||
|
pub fn last_raw_input(&self) -> &RawInput {
|
||||||
|
&self.last_raw_input
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style(&self) -> Style {
|
||||||
|
*self.style.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_style(&self, style: Style) {
|
||||||
|
*self.style.lock() = style;
|
||||||
|
}
|
||||||
|
|
||||||
/// Useful for pixel-perfect rendering
|
/// Useful for pixel-perfect rendering
|
||||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||||
(point * self.input.pixels_per_point).round() / self.input.pixels_per_point
|
(point * self.input.pixels_per_point).round() / self.input.pixels_per_point
|
||||||
|
@ -64,23 +89,6 @@ impl Context {
|
||||||
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
|
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Raw input from last frame. Use `input()` instead.
|
|
||||||
pub fn last_raw_input(&self) -> &RawInput {
|
|
||||||
&self.last_raw_input
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input(&self) -> &GuiInput {
|
|
||||||
&self.input
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style(&self) -> Style {
|
|
||||||
*self.style.lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_style(&self, style: Style) {
|
|
||||||
*self.style.lock() = style;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move
|
// TODO: move
|
||||||
pub fn begin_frame(&mut self, gui_input: GuiInput) {
|
pub fn begin_frame(&mut self, gui_input: GuiInput) {
|
||||||
self.used_ids.lock().clear();
|
self.used_ids.lock().clear();
|
||||||
|
|
|
@ -12,8 +12,8 @@ struct Stats {
|
||||||
/// Encapsulates input, layout and painting for ease of use.
|
/// Encapsulates input, layout and painting for ease of use.
|
||||||
/// TODO: merge into Context
|
/// TODO: merge into Context
|
||||||
pub struct Emigui {
|
pub struct Emigui {
|
||||||
pub last_input: RawInput,
|
last_input: RawInput,
|
||||||
pub ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
stats: Stats,
|
stats: Stats,
|
||||||
mesher_options: MesherOptions,
|
mesher_options: MesherOptions,
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,17 @@ impl Emigui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ctx(&self) -> &Arc<Context> {
|
||||||
|
&self.ctx
|
||||||
|
}
|
||||||
|
|
||||||
pub fn texture(&self) -> &Texture {
|
pub fn texture(&self) -> &Texture {
|
||||||
self.ctx.fonts.texture()
|
self.ctx.fonts.texture()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_frame(&mut self, new_input: RawInput) {
|
pub fn begin_frame(&mut self, new_input: RawInput) {
|
||||||
if !self.last_input.mouse_down || self.last_input.mouse_pos.is_none() {
|
if !self.last_input.mouse_down || self.last_input.mouse_pos.is_none() {
|
||||||
self.ctx.memory.lock().active_id = None;
|
self.ctx.memory().active_id = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let gui_input = GuiInput::from_last_and_new(&self.last_input, &new_input);
|
let gui_input = GuiInput::from_last_and_new(&self.last_input, &new_input);
|
||||||
|
|
|
@ -31,7 +31,7 @@ use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
||||||
|
|
||||||
use crate::math::Pos2;
|
use crate::math::Pos2;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct Id(u64);
|
pub struct Id(u64);
|
||||||
|
|
||||||
impl Id {
|
impl Id {
|
||||||
|
|
|
@ -5,12 +5,15 @@ use crate::{
|
||||||
Id, Layer, Pos2, Rect,
|
Id, Layer, Pos2, Rect,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
/// The widget being interacted with (e.g. dragged, in case of a slider).
|
/// The widget being interacted with (e.g. dragged, in case of a slider).
|
||||||
|
#[serde(skip)]
|
||||||
pub(crate) active_id: Option<Id>,
|
pub(crate) active_id: Option<Id>,
|
||||||
|
|
||||||
/// The widget with keyboard focus (i.e. a text input field).
|
/// The widget with keyboard focus (i.e. a text input field).
|
||||||
|
#[serde(skip)]
|
||||||
pub(crate) kb_focus_id: Option<Id>,
|
pub(crate) kb_focus_id: Option<Id>,
|
||||||
|
|
||||||
// states of various types of widgets
|
// states of various types of widgets
|
||||||
|
@ -24,11 +27,11 @@ pub struct Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
pub fn get_floating(&mut self, id: Id) -> Option<floating::State> {
|
pub(crate) fn get_floating(&mut self, id: Id) -> Option<floating::State> {
|
||||||
self.floating.get(&id).cloned()
|
self.floating.get(&id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_floating_state(&mut self, id: Id, state: floating::State) {
|
pub(crate) fn set_floating_state(&mut self, id: Id, state: floating::State) {
|
||||||
let did_insert = self.floating.insert(id, state).is_none();
|
let did_insert = self.floating.insert(id, state).is_none();
|
||||||
if did_insert {
|
if did_insert {
|
||||||
self.floating_order.push(id);
|
self.floating_order.push(id);
|
||||||
|
|
|
@ -119,11 +119,11 @@ impl Region {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn memory(&self) -> parking_lot::MutexGuard<Memory> {
|
pub fn memory(&self) -> parking_lot::MutexGuard<Memory> {
|
||||||
self.ctx.memory.lock()
|
self.ctx.memory()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn output(&self) -> parking_lot::MutexGuard<Output> {
|
pub fn output(&self) -> parking_lot::MutexGuard<Output> {
|
||||||
self.ctx.output.lock()
|
self.ctx.output()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fonts(&self) -> &Fonts {
|
pub fn fonts(&self) -> &Fonts {
|
||||||
|
|
|
@ -101,10 +101,10 @@ impl Widget for Hyperlink {
|
||||||
let (text, text_size) = font.layout_multiline(&self.text, region.available_width());
|
let (text, text_size) = font.layout_multiline(&self.text, region.available_width());
|
||||||
let interact = region.reserve_space(text_size, Some(id));
|
let interact = region.reserve_space(text_size, Some(id));
|
||||||
if interact.hovered {
|
if interact.hovered {
|
||||||
region.ctx().output.lock().cursor_icon = CursorIcon::PointingHand;
|
region.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
||||||
}
|
}
|
||||||
if interact.clicked {
|
if interact.clicked {
|
||||||
region.ctx().output.lock().open_url = Some(self.url);
|
region.ctx().output().open_url = Some(self.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if interact.hovered {
|
if interact.hovered {
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl<'t> Widget for TextEdit<'t> {
|
||||||
match event {
|
match event {
|
||||||
Event::Copy | Event::Cut => {
|
Event::Copy | Event::Cut => {
|
||||||
// TODO: cut
|
// TODO: cut
|
||||||
region.ctx().output.lock().copied_text = self.text.clone();
|
region.ctx().output().copied_text = self.text.clone();
|
||||||
}
|
}
|
||||||
Event::Text(text) => {
|
Event::Text(text) => {
|
||||||
if text == "\u{7f}" {
|
if text == "\u{7f}" {
|
||||||
|
|
|
@ -9,4 +9,5 @@ emigui = { path = "../emigui" }
|
||||||
|
|
||||||
clipboard = "0.5"
|
clipboard = "0.5"
|
||||||
glium = "0.24"
|
glium = "0.24"
|
||||||
|
serde_json = "1"
|
||||||
webbrowser = "0.5"
|
webbrowser = "0.5"
|
||||||
|
|
|
@ -165,3 +165,32 @@ pub fn handle_output(
|
||||||
.gl_window()
|
.gl_window()
|
||||||
.set_cursor(translate_cursor(output.cursor_icon));
|
.set_cursor(translate_cursor(output.cursor_icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn read_memory(ctx: &Context, memory_json_path: impl AsRef<std::path::Path>) {
|
||||||
|
match std::fs::File::open(memory_json_path) {
|
||||||
|
Ok(file) => {
|
||||||
|
let reader = std::io::BufReader::new(file);
|
||||||
|
match serde_json::from_reader(reader) {
|
||||||
|
Ok(memory) => {
|
||||||
|
*ctx.memory() = memory;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("ERROR: Failed to parse memory json: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("ERROR: Failed to read memory file: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_memory(
|
||||||
|
ctx: &Context,
|
||||||
|
memory_json_path: impl AsRef<std::path::Path>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
serde_json::to_writer(std::fs::File::create(memory_json_path)?, &*ctx.memory())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -37,3 +37,30 @@ pub fn local_storage_set(key: &str, value: &str) {
|
||||||
pub fn local_storage_remove(key: &str) {
|
pub fn local_storage_remove(key: &str) {
|
||||||
local_storage().map(|storage| storage.remove_item(key));
|
local_storage().map(|storage| storage.remove_item(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_memory(ctx: &emigui::Context) {
|
||||||
|
if let Some(memory_string) = local_storage_get("emigui_memory_json") {
|
||||||
|
match serde_json::from_str(&memory_string) {
|
||||||
|
Ok(memory) => {
|
||||||
|
*ctx.memory() = memory;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
console_log(format!("ERROR: Failed to parse memory json: {}", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_memory(ctx: &emigui::Context) {
|
||||||
|
match serde_json::to_string(&*ctx.memory()) {
|
||||||
|
Ok(json) => {
|
||||||
|
local_storage_set("emigui_memory_json", &json);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
console_log(format!(
|
||||||
|
"ERROR: Failed to seriealize memory as json: {}",
|
||||||
|
err
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,9 @@ fn main() {
|
||||||
let mut example_app = ExampleWindow::default();
|
let mut example_app = ExampleWindow::default();
|
||||||
let mut clipboard = emigui_glium::init_clipboard();
|
let mut clipboard = emigui_glium::init_clipboard();
|
||||||
|
|
||||||
|
let memory_path = "emigui.json";
|
||||||
|
emigui_glium::read_memory(&emigui.ctx(), memory_path);
|
||||||
|
|
||||||
while running {
|
while running {
|
||||||
{
|
{
|
||||||
// Keep smooth frame rate. TODO: proper vsync
|
// Keep smooth frame rate. TODO: proper vsync
|
||||||
|
@ -113,6 +116,10 @@ fn main() {
|
||||||
painter.paint_batches(&display, paint_batches, emigui.texture());
|
painter.paint_batches(&display, paint_batches, emigui.texture());
|
||||||
emigui_glium::handle_output(output, &display, clipboard.as_mut());
|
emigui_glium::handle_output(output, &display, clipboard.as_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(err) = emigui_glium::write_memory(&emigui.ctx(), memory_path) {
|
||||||
|
eprintln!("ERROR: Failed to save emigui state: {}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mean_frame_time(frame_times: &VecDeque<f64>) -> f64 {
|
pub fn mean_frame_time(frame_times: &VecDeque<f64>) -> f64 {
|
||||||
|
|
|
@ -31,9 +31,11 @@ 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> {
|
||||||
|
let emigui = Emigui::new(pixels_per_point);
|
||||||
|
emigui_wasm::load_memory(emigui.ctx());
|
||||||
Ok(State {
|
Ok(State {
|
||||||
example_app: Default::default(),
|
example_app: Default::default(),
|
||||||
emigui: Emigui::new(pixels_per_point),
|
emigui,
|
||||||
webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?,
|
webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?,
|
||||||
frame_times: Default::default(),
|
frame_times: Default::default(),
|
||||||
})
|
})
|
||||||
|
@ -111,6 +113,8 @@ impl State {
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
emigui_wasm::save_memory(self.emigui.ctx()); // TODO: don't save every frame
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue