[eframe] Make persistence, http and time optional features

Saves on compile times.
This commit is contained in:
Emil Ernerfeldt 2021-01-04 01:44:02 +01:00
parent 00269f96c0
commit 69d31a5e47
30 changed files with 412 additions and 303 deletions

2
Cargo.lock generated
View file

@ -692,7 +692,6 @@ version = "0.1.0"
dependencies = [
"eframe",
"egui_demo_lib",
"serde",
]
[[package]]
@ -704,7 +703,6 @@ dependencies = [
"epi",
"image",
"serde",
"serde_json",
"syntect",
]

View file

@ -1,15 +1,15 @@
#!/bin/bash
set -eu
cargo check --workspace --all-targets --all-features --release
cargo check --workspace --all-targets
cargo check --workspace --all-targets --all-features
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
cargo fmt --all -- --check
CARGO_INCREMENTAL=0 cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::all #-W clippy::pedantic -W clippy::restriction -W clippy::nursery
cargo test --workspace --all-targets --all-features
cargo test --workspace --doc
cargo check -p egui_web --lib --target wasm32-unknown-unknown
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown
# For finding bloat:
# cargo bloat --release --bin demo_glium -n 200 | rg egui

View file

@ -15,13 +15,19 @@ include = [ "**/*.rs", "Cargo.toml"]
[lib]
[dependencies]
egui = { version = "0.6.0", path = "../egui", features = ["serde"] }
epi = { version = "0.6.0", path = "../epi", features = ["serde", "serde_json"] }
egui = { version = "0.6.0", path = "../egui" }
epi = { version = "0.6.0", path = "../epi" }
# For compiling natively:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui_glium = { path = "../egui_glium", features = ["http"] }
egui_glium = { path = "../egui_glium" }
# For compiling to web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
egui_web = { path = "../egui_web" }
[features]
default = []
http = ["egui_glium/http", "egui_web/http"]
persistence = ["epi/persistence", "egui_glium/persistence", "egui_web/persistence"]
time = ["egui_glium/time"] # for seconds_since_midnight

View file

@ -9,6 +9,10 @@ edition = "2018"
crate-type = ["cdylib", "rlib"]
[dependencies]
eframe = { version = "0.6.0", path = "../eframe"}
egui_demo_lib = { version = "0.6.0", path = "../egui_demo_lib"}
serde = { version = "1", features = ["derive"] }
eframe = { version = "0.6.0", path = "../eframe", features = ["time"] }
egui_demo_lib = { version = "0.6.0", path = "../egui_demo_lib" }
[features]
default = []
http = ["eframe/http", "egui_demo_lib/http"]
persistence = ["eframe/persistence", "egui_demo_lib/persistence"]

View file

@ -15,18 +15,24 @@ include = [ "**/*.rs", "Cargo.toml"]
[lib]
[dependencies]
egui = { version = "0.6.0", path = "../egui", features = ["serde"] }
epi = { version = "0.6.0", path = "../epi", features = ["serde", "serde_json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
egui = { version = "0.6.0", path = "../egui" }
epi = { version = "0.6.0", path = "../epi" }
# Http fetch app:
image = { version = "0.23", default_features = false, features = ["jpeg", "png"] }
syntect = { version = "4", default_features = false, features = ["default-fancy"] }
# feature "http":
image = { version = "0.23", default_features = false, features = ["jpeg", "png"], optional = true }
syntect = { version = "4", default_features = false, features = ["default-fancy"], optional = true }
# feature "persistence":
serde = { version = "1", features = ["derive"], optional = true }
[dev-dependencies]
criterion = { version = "0.3", default-features = false }
[features]
default = []
http = ["image", "syntect", "epi/http"]
persistence = ["epi/persistence", "serde"]
[[bench]]
name = "benchmark"
harness = false

View file

@ -9,9 +9,9 @@ const RED: Color32 = Color32::RED;
const TRANSPARENT: Color32 = Color32::TRANSPARENT;
const WHITE: Color32 = Color32::WHITE;
#[derive(serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct ColorTest {
#[serde(skip)]
#[cfg_attr(feature = "persistence", serde(skip))]
tex_mngr: TextureManager,
vertex_gradients: bool,
texture_gradients: bool,

View file

@ -2,8 +2,9 @@
///
/// Implements `epi::App` so it can be used with
/// [`egui_glium`](https://crates.io/crates/egui_glium) and [`egui_web`](https://crates.io/crates/egui_web).
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct DemoApp {
demo_windows: super::DemoWindows,
}
@ -13,10 +14,12 @@ impl epi::App for DemoApp {
"✨ Egui Demo"
}
#[cfg(feature = "persistence")]
fn load(&mut self, storage: &dyn epi::Storage) {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
}
#[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self);
}

View file

@ -1,7 +1,7 @@
use egui::{containers::*, *};
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct DancingStrings {}
impl Default for DancingStrings {

View file

@ -2,8 +2,8 @@ use super::*;
use egui::{color::*, *};
/// Showcase some ui code
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct DemoWindow {
num_columns: usize,
@ -106,8 +106,9 @@ impl DemoWindow {
// ----------------------------------------------------------------------------
#[derive(PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
struct ColorWidgets {
srgba_unmul: [u8; 4],
srgba_premul: [u8; 4],
@ -176,8 +177,8 @@ impl ColorWidgets {
// ----------------------------------------------------------------------------
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
struct BoxPainting {
size: Vec2,
corner_radius: f32,
@ -220,8 +221,8 @@ impl BoxPainting {
// ----------------------------------------------------------------------------
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
struct LayoutDemo {
// Identical to contents of `egui::Layout`
main_dir: Direction,
@ -363,7 +364,8 @@ enum Action {
Delete,
}
#[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
#[derive(Clone, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct Tree(Vec<Tree>);
impl Tree {

View file

@ -2,11 +2,11 @@ use egui::{CtxRef, Resize, ScrollArea, Ui, Window};
// ----------------------------------------------------------------------------
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
struct Demos {
/// open, view
#[serde(skip)] // TODO: serialize the `open` state.
#[cfg_attr(feature = "persistence", serde(skip))] // TODO: serialize the `open` state.
demos: Vec<(bool, Box<dyn super::Demo>)>,
}
impl Default for Demos {
@ -40,8 +40,9 @@ impl Demos {
// ----------------------------------------------------------------------------
/// A menu bar in which you can select different demo windows to show.
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct DemoWindows {
open_windows: OpenWindows,
@ -189,7 +190,7 @@ impl DemoWindows {
// ----------------------------------------------------------------------------
#[derive(serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct OpenWindows {
demo: bool,

View file

@ -1,7 +1,7 @@
use egui::*;
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Painting {
lines: Vec<Vec<Vec2>>,
stroke: Stroke,

View file

@ -1,7 +1,7 @@
use egui::{color::*, *};
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Scrolls {
track_item: usize,
tracking: bool,

View file

@ -2,8 +2,9 @@ use egui::*;
use std::f64::INFINITY;
/// Showcase sliders
#[derive(PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Sliders {
pub min: f64,
pub max: f64,

View file

@ -1,6 +1,7 @@
use egui::{color::*, *};
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
enum Enum {
First,
Second,
@ -13,8 +14,8 @@ impl Default for Enum {
}
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Widgets {
button_enabled: bool,
count: usize,

View file

@ -1,6 +1,7 @@
use crate::__egui_github_link_file;
#[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize)]
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct WindowOptions {
title: String,
title_bar: bool,

View file

@ -1,8 +1,9 @@
use egui::{containers::*, widgets::*, *};
use std::f32::consts::TAU;
#[derive(PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct FractalClock {
paused: bool,
time: f64,

View file

@ -30,17 +30,17 @@ impl Resource {
}
}
#[derive(serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct HttpApp {
url: String,
#[serde(skip)]
#[cfg_attr(feature = "persistence", serde(skip))]
in_progress: Option<Receiver<Result<Response, String>>>,
#[serde(skip)]
#[cfg_attr(feature = "persistence", serde(skip))]
result: Option<Result<Resource, String>>,
#[serde(skip)]
#[cfg_attr(feature = "persistence", serde(skip))]
tex_mngr: TexMngr,
}

View file

@ -1,11 +1,13 @@
mod color_test;
mod demo;
mod fractal_clock;
#[cfg(feature = "http")]
mod http_app;
pub use color_test::ColorTest;
pub use demo::DemoApp;
pub use fractal_clock::FractalClock;
#[cfg(feature = "http")]
pub use http_app::HttpApp;
pub use demo::DemoWindows; // used for tests

View file

@ -1,8 +1,10 @@
/// All the different demo apps.
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Apps {
demo: crate::apps::DemoApp,
#[cfg(feature = "http")]
http: crate::apps::HttpApp,
clock: crate::apps::FractalClock,
color_test: crate::apps::ColorTest,
@ -12,6 +14,7 @@ impl Apps {
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut dyn epi::App)> {
vec![
("demo", &mut self.demo as &mut dyn epi::App),
#[cfg(feature = "http")]
("http", &mut self.http as &mut dyn epi::App),
("clock", &mut self.clock as &mut dyn epi::App),
("colors", &mut self.color_test as &mut dyn epi::App),
@ -21,8 +24,9 @@ impl Apps {
}
/// Wraps many demo/test apps into one.
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct WrapApp {
selected_anchor: String,
apps: Apps,
@ -34,10 +38,12 @@ impl epi::App for WrapApp {
"Egui Demo Apps"
}
#[cfg(feature = "persistence")]
fn load(&mut self, storage: &dyn epi::Storage) {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
}
#[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self);
}
@ -167,18 +173,20 @@ impl Default for RunMode {
// ----------------------------------------------------------------------------
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[derive(Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
struct BackendPanel {
open: bool,
#[serde(skip)] // go back to `Reactive` mode each time we start
#[cfg_attr(feature = "persistence", serde(skip))]
// go back to `Reactive` mode each time we start
run_mode: RunMode,
/// current slider value for current gui scale
pixels_per_point: Option<f32>,
#[serde(skip)]
#[cfg_attr(feature = "persistence", serde(skip))]
frame_history: crate::frame_history::FrameHistory,
}

View file

@ -13,17 +13,32 @@ keywords = ["glium", "egui", "gui", "gamedev"]
include = [ "**/*.rs", "Cargo.toml"]
[dependencies]
chrono = { version = "0.4" }
clipboard = "0.5"
directories-next = "2"
egui = { version = "0.6.0", path = "../egui", features = ["serde"] }
epi = { version = "0.6.0", path = "../epi", features = ["serde", "serde_json"] }
egui = { version = "0.6.0", path = "../egui" }
epi = { version = "0.6.0", path = "../epi" }
glium = "0.29"
serde = "1"
serde_json = "1"
ureq = { version = "1.5", optional = true }
webbrowser = "0.5"
# feature "http":
ureq = { version = "1.5", optional = true }
# feature "persistence":
directories-next = { version = "2", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
# feature "time"
chrono = { version = "0.4", optional = true }
[features]
default = []
http = ["ureq"]
persistence = [
"directories-next",
"egui/serde",
"epi/serde_json",
"epi/serde",
"serde_json",
"serde",
]
time = ["chrono"] # for seconds_since_midnight

View file

@ -1,12 +1,32 @@
use crate::{window_settings::WindowSettings, *};
use egui::Color32;
use std::time::Instant;
use crate::{storage::WindowSettings, *};
pub use egui::Color32;
#[cfg(feature = "persistence")]
const EGUI_MEMORY_KEY: &str = "egui";
#[cfg(feature = "persistence")]
const WINDOW_KEY: &str = "window";
#[cfg(feature = "persistence")]
fn deserialize_window_settings(storage: &Option<Box<dyn epi::Storage>>) -> Option<WindowSettings> {
epi::get_value(&**storage.as_ref()?, WINDOW_KEY)
}
#[cfg(not(feature = "persistence"))]
fn deserialize_window_settings(_: &Option<Box<dyn epi::Storage>>) -> Option<WindowSettings> {
None
}
#[cfg(feature = "persistence")]
fn deserialize_memory(storage: &Option<Box<dyn epi::Storage>>) -> Option<egui::Memory> {
epi::get_value(&**storage.as_ref()?, EGUI_MEMORY_KEY)
}
#[cfg(not(feature = "persistence"))]
fn deserialize_memory(_: &Option<Box<dyn epi::Storage>>) -> Option<egui::Memory> {
None
}
impl epi::TextureAllocator for Painter {
fn alloc(&mut self) -> egui::TextureId {
self.alloc_user_texture()
@ -69,6 +89,12 @@ fn create_display(
display
}
#[cfg(not(feature = "persistence"))]
fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
None
}
#[cfg(feature = "persistence")]
fn create_storage(app_name: &str) -> Option<Box<dyn epi::Storage>> {
if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) {
let data_dir = proj_dirs.data_dir().to_path_buf();
@ -81,7 +107,7 @@ fn create_storage(app_name: &str) -> Option<Box<dyn epi::Storage>> {
} else {
let mut config_dir = data_dir;
config_dir.push("app.json");
let storage = crate::storage::FileStorage::from_path(config_dir);
let storage = crate::persistence::FileStorage::from_path(config_dir);
Some(Box::new(storage))
}
} else {
@ -97,7 +123,7 @@ fn integration_info(
epi::IntegrationInfo {
web_info: None,
cpu_usage: previous_frame_time,
seconds_since_midnight: Some(seconds_since_midnight()),
seconds_since_midnight: seconds_since_midnight(),
native_pixels_per_point: Some(native_pixels_per_point(&display)),
}
}
@ -110,9 +136,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
app.load(storage.as_ref());
}
let window_settings: Option<WindowSettings> = storage
.as_mut()
.and_then(|storage| epi::get_value(storage.as_ref(), WINDOW_KEY));
let window_settings = deserialize_window_settings(&storage);
let event_loop = glutin::event_loop::EventLoop::with_user_event();
let display = create_display(app.name(), window_settings, app.is_resizable(), &event_loop);
@ -121,10 +145,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
)));
let mut ctx = egui::CtxRef::default();
*ctx.memory() = storage
.as_mut()
.and_then(|storage| epi::get_value(storage.as_ref(), EGUI_MEMORY_KEY))
.unwrap_or_default();
*ctx.memory() = deserialize_memory(&storage).unwrap_or_default();
app.setup(&ctx);
@ -135,8 +156,10 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
let mut painter = Painter::new(&display);
let mut clipboard = init_clipboard();
#[cfg(feature = "persistence")]
let mut last_auto_save = Instant::now();
#[cfg(feature = "http")]
let http = std::sync::Arc::new(crate::http::GliumHttp {});
if app.warm_up_enabled() {
@ -151,6 +174,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
let mut frame = epi::backend::FrameBuilder {
info: integration_info(&display, None),
tex_allocator: Some(&mut painter),
#[cfg(feature = "http")]
http: http.clone(),
output: &mut app_output,
repaint_signal: repaint_signal.clone(),
@ -183,6 +207,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
let mut frame = epi::backend::FrameBuilder {
info: integration_info(&display, previous_frame_time),
tex_allocator: Some(&mut painter),
#[cfg(feature = "http")]
http: http.clone(),
output: &mut app_output,
repaint_signal: repaint_signal.clone(),
@ -236,6 +261,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
handle_output(egui_output, &display, clipboard.as_mut());
#[cfg(feature = "persistence")]
if let Some(storage) = &mut storage {
let now = Instant::now();
if now - last_auto_save > app.auto_save_interval() {
@ -265,6 +291,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
}
glutin::event::Event::LoopDestroyed => {
app.on_exit();
#[cfg(feature = "persistence")]
if let Some(storage) = &mut storage {
epi::set_value(
storage.as_mut(),

View file

@ -13,7 +13,9 @@ mod backend;
#[cfg(feature = "http")]
pub mod http;
mod painter;
pub mod storage;
#[cfg(feature = "persistence")]
pub mod persistence;
pub mod window_settings;
pub use backend::*;
pub use painter::Painter;
@ -279,10 +281,17 @@ pub fn init_clipboard() -> Option<ClipboardContext> {
// ----------------------------------------------------------------------------
/// Time of day as seconds since midnight. Used for clock in demo app.
pub fn seconds_since_midnight() -> f64 {
use chrono::Timelike;
let time = chrono::Local::now().time();
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64)
pub fn seconds_since_midnight() -> Option<f64> {
#[cfg(feature = "time")]
{
use chrono::Timelike;
let time = chrono::Local::now().time();
let seconds_since_midnight =
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64);
Some(seconds_since_midnight)
}
#[cfg(not(feature = "time"))]
None
}
pub fn screen_size_in_pixels(display: &glium::Display) -> Vec2 {

View file

@ -0,0 +1,87 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
// ----------------------------------------------------------------------------
/// A key-value store backed by a JSON file on disk.
/// Used to restore egui state, glium window position/size and app state.
pub struct FileStorage {
path: PathBuf,
kv: HashMap<String, String>,
dirty: bool,
}
impl FileStorage {
pub fn from_path(path: impl Into<PathBuf>) -> Self {
let path: PathBuf = path.into();
Self {
kv: read_json(&path).unwrap_or_default(),
path,
dirty: false,
}
}
}
impl epi::Storage for FileStorage {
fn get_string(&self, key: &str) -> Option<String> {
self.kv.get(key).cloned()
}
fn set_string(&mut self, key: &str, value: String) {
if self.kv.get(key) != Some(&value) {
self.kv.insert(key.to_owned(), value);
self.dirty = true;
}
}
fn flush(&mut self) {
if self.dirty {
serde_json::to_writer(std::fs::File::create(&self.path).unwrap(), &self.kv).unwrap();
self.dirty = false;
}
}
}
// ----------------------------------------------------------------------------
pub fn read_json<T>(memory_json_path: impl AsRef<Path>) -> Option<T>
where
T: serde::de::DeserializeOwned,
{
match std::fs::File::open(memory_json_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
match serde_json::from_reader(reader) {
Ok(value) => Some(value),
Err(err) => {
eprintln!("ERROR: Failed to parse json: {}", err);
None
}
}
}
Err(_err) => {
// File probably doesn't exist. That's fine.
None
}
}
}
// ----------------------------------------------------------------------------
/// Alternative to `FileStorage`
pub fn read_memory(ctx: &egui::Context, memory_json_path: impl AsRef<std::path::Path>) {
let memory: Option<egui::Memory> = read_json(memory_json_path);
if let Some(memory) = memory {
*ctx.memory() = memory;
}
}
/// Alternative to `FileStorage`
pub fn write_memory(
ctx: &egui::Context,
memory_json_path: impl AsRef<std::path::Path>,
) -> Result<(), Box<dyn std::error::Error>> {
serde_json::to_writer_pretty(std::fs::File::create(memory_json_path)?, &*ctx.memory())?;
Ok(())
}

View file

@ -1,175 +0,0 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
// ----------------------------------------------------------------------------
/// A key-value store backed by a JSON file on disk.
/// Used to restore egui state, glium window position/size and app state.
pub struct FileStorage {
path: PathBuf,
kv: HashMap<String, String>,
dirty: bool,
}
impl FileStorage {
pub fn from_path(path: impl Into<PathBuf>) -> Self {
let path: PathBuf = path.into();
Self {
kv: read_json(&path).unwrap_or_default(),
path,
dirty: false,
}
}
}
impl epi::Storage for FileStorage {
fn get_string(&self, key: &str) -> Option<String> {
self.kv.get(key).cloned()
}
fn set_string(&mut self, key: &str, value: String) {
if self.kv.get(key) != Some(&value) {
self.kv.insert(key.to_owned(), value);
self.dirty = true;
}
}
fn flush(&mut self) {
if self.dirty {
serde_json::to_writer(std::fs::File::create(&self.path).unwrap(), &self.kv).unwrap();
self.dirty = false;
}
}
}
// ----------------------------------------------------------------------------
pub fn read_json<T>(memory_json_path: impl AsRef<Path>) -> Option<T>
where
T: serde::de::DeserializeOwned,
{
match std::fs::File::open(memory_json_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
match serde_json::from_reader(reader) {
Ok(value) => Some(value),
Err(err) => {
eprintln!("ERROR: Failed to parse json: {}", err);
None
}
}
}
Err(_err) => {
// File probably doesn't exist. That's fine.
None
}
}
}
// ----------------------------------------------------------------------------
/// Alternative to `FileStorage`
pub fn read_memory(ctx: &egui::Context, memory_json_path: impl AsRef<std::path::Path>) {
let memory: Option<egui::Memory> = read_json(memory_json_path);
if let Some(memory) = memory {
*ctx.memory() = memory;
}
}
/// Alternative to `FileStorage`
pub fn write_memory(
ctx: &egui::Context,
memory_json_path: impl AsRef<std::path::Path>,
) -> Result<(), Box<dyn std::error::Error>> {
serde_json::to_writer_pretty(std::fs::File::create(memory_json_path)?, &*ctx.memory())?;
Ok(())
}
// ----------------------------------------------------------------------------
use glium::glutin;
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct WindowSettings {
/// outer position of window in physical pixels
pos: Option<egui::Pos2>,
/// Inner size of window in logical pixels
inner_size_points: Option<egui::Vec2>,
}
impl WindowSettings {
pub fn from_json_file(
settings_json_path: impl AsRef<std::path::Path>,
) -> Option<WindowSettings> {
read_json(settings_json_path)
}
pub fn from_display(display: &glium::Display) -> Self {
let scale_factor = display.gl_window().window().scale_factor();
let inner_size_points = display
.gl_window()
.window()
.inner_size()
.to_logical::<f32>(scale_factor);
Self {
pos: display
.gl_window()
.window()
.outer_position()
.ok()
.map(|p| egui::pos2(p.x as f32, p.y as f32)),
inner_size_points: Some(egui::vec2(
inner_size_points.width as f32,
inner_size_points.height as f32,
)),
}
}
pub fn initialize_size(
&self,
window: glutin::window::WindowBuilder,
) -> glutin::window::WindowBuilder {
if let Some(inner_size_points) = self.inner_size_points {
window.with_inner_size(glutin::dpi::LogicalSize {
width: inner_size_points.x as f64,
height: inner_size_points.y as f64,
})
} else {
window
}
// Not yet available in winit: https://github.com/rust-windowing/winit/issues/1190
// if let Some(pos) = self.pos {
// *window = window.with_outer_pos(glutin::dpi::PhysicalPosition {
// x: pos.x as f64,
// y: pos.y as f64,
// });
// }
}
pub fn restore_positions(&self, display: &glium::Display) {
// not needed, done by `initialize_size`
// let size = self.size.unwrap_or_else(|| vec2(1024.0, 800.0));
// display
// .gl_window()
// .window()
// .set_inner_size(glutin::dpi::PhysicalSize {
// width: size.x as f64,
// height: size.y as f64,
// });
if let Some(pos) = self.pos {
display
.gl_window()
.window()
.set_outer_position(glutin::dpi::PhysicalPosition::new(
pos.x as f64,
pos.y as f64,
));
}
}
}

View file

@ -0,0 +1,85 @@
use glium::glutin;
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct WindowSettings {
/// outer position of window in physical pixels
pos: Option<egui::Pos2>,
/// Inner size of window in logical pixels
inner_size_points: Option<egui::Vec2>,
}
impl WindowSettings {
#[cfg(feature = "persistence")]
pub fn from_json_file(
settings_json_path: impl AsRef<std::path::Path>,
) -> Option<WindowSettings> {
crate::persistence::read_json(settings_json_path)
}
pub fn from_display(display: &glium::Display) -> Self {
let scale_factor = display.gl_window().window().scale_factor();
let inner_size_points = display
.gl_window()
.window()
.inner_size()
.to_logical::<f32>(scale_factor);
Self {
pos: display
.gl_window()
.window()
.outer_position()
.ok()
.map(|p| egui::pos2(p.x as f32, p.y as f32)),
inner_size_points: Some(egui::vec2(
inner_size_points.width as f32,
inner_size_points.height as f32,
)),
}
}
pub fn initialize_size(
&self,
window: glutin::window::WindowBuilder,
) -> glutin::window::WindowBuilder {
if let Some(inner_size_points) = self.inner_size_points {
window.with_inner_size(glutin::dpi::LogicalSize {
width: inner_size_points.x as f64,
height: inner_size_points.y as f64,
})
} else {
window
}
// Not yet available in winit: https://github.com/rust-windowing/winit/issues/1190
// if let Some(pos) = self.pos {
// *window = window.with_outer_pos(glutin::dpi::PhysicalPosition {
// x: pos.x as f64,
// y: pos.y as f64,
// });
// }
}
pub fn restore_positions(&self, display: &glium::Display) {
// not needed, done by `initialize_size`
// let size = self.size.unwrap_or_else(|| vec2(1024.0, 800.0));
// display
// .gl_window()
// .window()
// .set_inner_size(glutin::dpi::PhysicalSize {
// width: size.x as f64,
// height: size.y as f64,
// });
if let Some(pos) = self.pos {
display
.gl_window()
.window()
.set_outer_position(glutin::dpi::PhysicalPosition::new(
pos.x as f64,
pos.y as f64,
));
}
}
}

View file

@ -16,47 +16,54 @@ include = [ "**/*.rs", "Cargo.toml"]
crate-type = ["cdylib", "rlib"]
[dependencies]
egui = { version = "0.6.0", path = "../egui", features = ["serde"] }
epi = { version = "0.6.0", path = "../epi", features = ["serde", "serde_json"] }
egui = { version = "0.6.0", path = "../egui" }
epi = { version = "0.6.0", path = "../epi" }
js-sys = "0.3"
serde = "1"
serde_json = "1"
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
[features]
default = []
http = [
"epi/http",
"web-sys/Headers",
"web-sys/Request",
"web-sys/RequestInit",
"web-sys/RequestMode",
"web-sys/Response",
]
persistence = ["serde", "serde_json"]
[dependencies.web-sys]
version = "0.3"
features = [
'Clipboard',
'ClipboardEvent',
'console',
'CssStyleDeclaration',
'DataTransfer',
'Document',
'DomRect',
'Element',
'Headers',
'HtmlCanvasElement',
'HtmlElement',
'KeyboardEvent',
'Location',
'MouseEvent',
'Navigator',
'Performance',
'Request',
'RequestInit',
'RequestMode',
'Response',
'Storage',
'Touch',
'TouchEvent',
'TouchList',
'WebGlBuffer',
'WebGlProgram',
'WebGlRenderingContext',
'WebGlShader',
'WebGlTexture',
'WebGlUniformLocation',
'WheelEvent',
'Window',
"Clipboard",
"ClipboardEvent",
"console",
"CssStyleDeclaration",
"DataTransfer",
"Document",
"DomRect",
"Element",
"HtmlCanvasElement",
"HtmlElement",
"KeyboardEvent",
"Location",
"MouseEvent",
"Navigator",
"Performance",
"Storage",
"Touch",
"TouchEvent",
"TouchList",
"WebGlBuffer",
"WebGlProgram",
"WebGlRenderingContext",
"WebGlShader",
"WebGlTexture",
"WebGlUniformLocation",
"WheelEvent",
"Window",
]

View file

@ -1,7 +1,6 @@
use crate::*;
pub use egui::{pos2, Color32};
use http::WebHttp;
// ----------------------------------------------------------------------------
@ -144,7 +143,8 @@ pub struct AppRunner {
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
storage: LocalStorage,
last_save_time: f64,
http: Arc<WebHttp>,
#[cfg(feature = "http")]
http: Arc<http::WebHttp>,
}
impl AppRunner {
@ -160,7 +160,8 @@ impl AppRunner {
needs_repaint: Default::default(),
storage,
last_save_time: now_sec(),
http: Arc::new(WebHttp {}),
#[cfg(feature = "http")]
http: Arc::new(http::WebHttp {}),
})
}
@ -210,6 +211,7 @@ impl AppRunner {
native_pixels_per_point: Some(native_pixels_per_point()),
},
tex_allocator: Some(&mut self.web_backend.painter),
#[cfg(feature = "http")]
http: self.http.clone(),
output: &mut app_output,
repaint_signal: self.needs_repaint.clone(),

View file

@ -9,6 +9,7 @@
#![warn(clippy::all, rust_2018_idioms)]
pub mod backend;
#[cfg(feature = "http")]
pub mod http;
pub mod webgl;
@ -154,6 +155,7 @@ pub fn local_storage_remove(key: &str) {
local_storage().map(|storage| storage.remove_item(key));
}
#[cfg(feature = "persistence")]
pub fn load_memory(ctx: &egui::Context) {
if let Some(memory_string) = local_storage_get("egui_memory_json") {
match serde_json::from_str(&memory_string) {
@ -167,6 +169,10 @@ pub fn load_memory(ctx: &egui::Context) {
}
}
#[cfg(not(feature = "persistence"))]
pub fn load_memory(_: &egui::Context) {}
#[cfg(feature = "persistence")]
pub fn save_memory(ctx: &egui::Context) {
match serde_json::to_string(&*ctx.memory()) {
Ok(json) => {
@ -178,6 +184,9 @@ pub fn save_memory(ctx: &egui::Context) {
}
}
#[cfg(not(feature = "persistence"))]
pub fn save_memory(_: &egui::Context) {}
#[derive(Default)]
pub struct LocalStorage {}

View file

@ -18,3 +18,8 @@ include = [ "**/*.rs", "Cargo.toml"]
egui = { version = "0.6.0", path = "../egui" }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
[features]
default = []
http = []
persistence = ["serde", "serde_json"]

View file

@ -150,6 +150,7 @@ impl<'a> Frame<'a> {
/// Very simple Http fetch API.
/// Calls the given callback when done.
#[cfg(feature = "http")]
pub fn http_fetch(
&self,
request: http::Request,
@ -257,6 +258,7 @@ pub const APP_KEY: &str = "app";
// ----------------------------------------------------------------------------
#[cfg(feature = "http")]
/// `epi` supports simple HTTP requests with [`Frame::http_fetch`].
pub mod http {
/// A simple http requests.
@ -310,6 +312,7 @@ pub mod backend {
use super::*;
/// Implements `Http` requests.
#[cfg(feature = "http")]
pub trait Http {
/// Calls the given callback when done.
fn fetch_dyn(
@ -326,6 +329,7 @@ pub mod backend {
/// A way to allocate textures (on integrations that support it).
pub tex_allocator: Option<&'a mut dyn TextureAllocator>,
/// Do http requests.
#[cfg(feature = "http")]
pub http: std::sync::Arc<dyn backend::Http>,
/// Where the app can issue commands back to the integration.
pub output: &'a mut AppOutput,