diff --git a/client/Cargo.toml b/client/Cargo.toml index 87c8051..0587771 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,6 +1,37 @@ [package] -name = "client" +name = "realm_client" version = "0.1.0" edition = "2021" +[package.metadata.docs.rs] +all-features = true +targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] + [dependencies] +egui = "0.27.2" +eframe = { version = "0.27.2", default-features = false, features = [ + "accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies. + "default_fonts", # Embed the default egui fonts. + "glow", # Use the glow rendering backend. Alternative: "wgpu". + "persistence", # Enable restoring app state when restarting the app. +] } +log = "0.4" + +# You only need serde if you want app persistence: +serde = { version = "1", features = ["derive"] } + +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +env_logger = "0.11.3" + +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = "0.4" + + +[profile.release] +opt-level = 2 # fast and small wasm + +# Optimize all dependencies even in debug builds: +[profile.dev.package."*"] +opt-level = 2 diff --git a/client/assets/favicon.ico b/client/assets/favicon.ico new file mode 100644 index 0000000..61ad031 Binary files /dev/null and b/client/assets/favicon.ico differ diff --git a/client/assets/icon-1024.png b/client/assets/icon-1024.png new file mode 100644 index 0000000..1b5868a Binary files /dev/null and b/client/assets/icon-1024.png differ diff --git a/client/assets/icon-256.png b/client/assets/icon-256.png new file mode 100644 index 0000000..ae72287 Binary files /dev/null and b/client/assets/icon-256.png differ diff --git a/client/assets/icon_ios_touch_192.png b/client/assets/icon_ios_touch_192.png new file mode 100644 index 0000000..8472802 Binary files /dev/null and b/client/assets/icon_ios_touch_192.png differ diff --git a/client/assets/manifest.json b/client/assets/manifest.json new file mode 100644 index 0000000..c1edc8e --- /dev/null +++ b/client/assets/manifest.json @@ -0,0 +1,28 @@ +{ + "name": "Realm Chat", + "short_name": "realm-chat-pwa", + "icons": [ + { + "src": "./icon-256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "./maskable_icon_x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "./icon-1024.png", + "sizes": "1024x1024", + "type": "image/png" + } + ], + "lang": "en-US", + "id": "/index.html", + "start_url": "./index.html", + "display": "standalone", + "background_color": "white", + "theme_color": "white" +} diff --git a/client/assets/maskable_icon_x512.png b/client/assets/maskable_icon_x512.png new file mode 100644 index 0000000..db8df3e Binary files /dev/null and b/client/assets/maskable_icon_x512.png differ diff --git a/client/assets/sw.js b/client/assets/sw.js new file mode 100644 index 0000000..7ecd229 --- /dev/null +++ b/client/assets/sw.js @@ -0,0 +1,25 @@ +var cacheName = 'egui-template-pwa'; +var filesToCache = [ + './', + './index.html', + './eframe_template.js', + './eframe_template_bg.wasm', +]; + +/* Start the service worker and cache all of the app's content */ +self.addEventListener('install', function (e) { + e.waitUntil( + caches.open(cacheName).then(function (cache) { + return cache.addAll(filesToCache); + }) + ); +}); + +/* Serve cached content when offline */ +self.addEventListener('fetch', function (e) { + e.respondWith( + caches.match(e.request).then(function (response) { + return response || fetch(e.request); + }) + ); +}); diff --git a/client/src/app.rs b/client/src/app.rs new file mode 100644 index 0000000..0ba27e4 --- /dev/null +++ b/client/src/app.rs @@ -0,0 +1,109 @@ +/// We derive Deserialize/Serialize so we can persist app state on shutdown. +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(default)] // if we add new fields, give them default values when deserializing old state +pub struct RealmApp { + // Example stuff: + label: String, + + #[serde(skip)] // This how you opt-out of serialization of a field + value: f32, +} + +impl Default for RealmApp { + fn default() -> Self { + Self { + // Example stuff: + label: "Hello World!".to_owned(), + value: 2.7, + } + } +} + +impl RealmApp { + /// Called once before the first frame. + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + // This is also where you can customize the look and feel of egui using + // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. + + // Load previous app state (if any). + // Note that you must enable the `persistence` feature for this to work. + if let Some(storage) = cc.storage { + return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + } + + Default::default() + } +} + +impl eframe::App for RealmApp { + /// Called by the frame work to save state before shutdown. + fn save(&mut self, storage: &mut dyn eframe::Storage) { + eframe::set_value(storage, eframe::APP_KEY, self); + } + + /// Called each time the UI needs repainting, which may be many times per second. + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`. + // For inspiration and more examples, go to https://emilk.github.io/egui + + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + // The top panel is often a good place for a menu bar: + + egui::menu::bar(ui, |ui| { + // NOTE: no File->Quit on web pages! + let is_web = cfg!(target_arch = "wasm32"); + if !is_web { + ui.menu_button("File", |ui| { + if ui.button("Quit").clicked() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + }); + ui.add_space(16.0); + } + + egui::widgets::global_dark_light_mode_buttons(ui); + }); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + // The central panel the region left after adding TopPanel's and SidePanel's + ui.heading("eframe template"); + + ui.horizontal(|ui| { + ui.label("Write something: "); + ui.text_edit_singleline(&mut self.label); + }); + + ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0).text("value")); + if ui.button("Increment").clicked() { + self.value += 1.0; + } + + ui.separator(); + + ui.add(egui::github_link_file!( + "https://github.com/emilk/eframe_template/blob/main/", + "Source code." + )); + + ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { + powered_by_egui_and_eframe(ui); + egui::warn_if_debug_build(ui); + }); + }); + } +} + +fn powered_by_egui_and_eframe(ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("Powered by "); + ui.hyperlink_to("egui", "https://github.com/emilk/egui"); + ui.label(" and "); + ui.hyperlink_to( + "eframe", + "https://github.com/emilk/egui/tree/master/crates/eframe", + ); + ui.label("."); + }); +} \ No newline at end of file diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100644 index 0000000..e9a1489 --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::all, rust_2018_idioms)] + +mod app; +pub use app::RealmApp; \ No newline at end of file diff --git a/client/src/main.rs b/client/src/main.rs index e7a11a9..ec32dc7 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,3 +1,45 @@ -fn main() { - println!("Hello, world!"); +#![warn(clippy::all, rust_2018_idioms)] +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +// When compiling natively: +#[cfg(not(target_arch = "wasm32"))] +fn main() -> eframe::Result<()> { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + + let native_options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([400.0, 300.0]) + .with_min_inner_size([300.0, 220.0]) + .with_icon( + // NOTE: Adding an icon is optional + eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) + .expect("Failed to load icon"), + ), + ..Default::default() + }; + eframe::run_native( + "Realm Chat", + native_options, + Box::new(|cc| Box::new(realm_client::RealmApp::new(cc))), + ) } + +// When compiling to web using trunk: +#[cfg(target_arch = "wasm32")] +fn main() { + // Redirect `log` message to `console.log` and friends: + eframe::WebLogger::init(log::LevelFilter::Debug).ok(); + + let web_options = eframe::WebOptions::default(); + + wasm_bindgen_futures::spawn_local(async { + eframe::WebRunner::new() + .start( + "the_canvas_id", // hardcode it + web_options, + Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))), + ) + .await + .expect("failed to start eframe"); + }); +} \ No newline at end of file