Menu
cosmic-mobile
publicLatest change 43b1f40498083af2bf229374298cf28e78c02f75 - Add minimal Rust Wayland shell by AkurAI Build
use eframe::{egui, glow::HasContext};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Default, PartialEq, Debug, Clone, Copy)]
enum Page { #[default] Home, Notes, System }
struct Shell { page: Page, notes: String, renderer: String }
fn clock(seconds: u64) -> String { format!("{:02}:{:02}", seconds / 3600 % 24, seconds / 60 % 60) }
impl eframe::App for Shell {
fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
ctx.request_repaint_after(Duration::from_secs(1));
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { self.page = Page::Home; }
egui::TopBottomPanel::bottom("navigation").frame(egui::Frame::new().fill(egui::Color32::from_rgb(23,28,34)).inner_margin(20)).show(ctx, |ui| {
ui.columns(3, |cols| { for (col, (label, page)) in cols.iter_mut().zip([("Home",Page::Home),("Notes",Page::Notes),("System",Page::System)]) {
if col.add_sized([col.available_width(),52.],egui::Button::new(label).selected(self.page == page)).clicked() { self.page = page; }
}});
});
egui::CentralPanel::default().frame(egui::Frame::new().fill(egui::Color32::from_rgb(23,28,34)).inner_margin(28)).show(ctx, |ui| {
ui.label(egui::RichText::new("COSMIC / MOBILE").size(16.).color(egui::Color32::from_rgb(156,207,218)));
ui.add_space(10.); ui.label("Rust shell · Wayland prototype"); ui.add_space(48.);
match self.page {
Page::Home => {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
ui.label(egui::RichText::new(clock(now)).size(76.)); ui.label("UTC");
ui.add_space(48.); ui.heading("Your space."); ui.add_space(12.);
ui.label("A small beginning. No desktop clutter.");
ui.add_space(32.);
if ui.add_sized([ui.available_width(),60.],egui::Button::new("Open scratchpad")).clicked() { self.page = Page::Notes; }
ui.add_space(16.); ui.label("Notes stay in memory for this session.");
}
Page::Notes => { ui.heading("Scratchpad"); ui.add_space(16.); ui.label("Session only — closing the shell clears this text."); ui.add_space(16.);
ui.add_sized([ui.available_width(),320.],egui::TextEdit::multiline(&mut self.notes).hint_text("Write something…").font(egui::TextStyle::Body));
}
Page::System => { ui.heading("System"); ui.add_space(24.);
ui.label("Graphics renderer"); ui.label(egui::RichText::new(&self.renderer).size(19.)); ui.add_space(24.);
ui.label(format!("Architecture: {}",std::env::consts::ARCH)); ui.label("Display: Wayland / OpenGL");
ui.add_space(24.); ui.label("VM hardware is virtual. This does not validate OnePlus 13R drivers.");
}
}
});
}
}
fn main() -> eframe::Result {
if std::env::var_os("WAYLAND_DISPLAY").is_none() { eprintln!("Wayland session required"); std::process::exit(1); }
let options = eframe::NativeOptions { renderer: eframe::Renderer::Glow, viewport: egui::ViewportBuilder::default().with_inner_size([450.,990.]).with_fullscreen(true), ..Default::default() };
eframe::run_native("cosmic-mobile", options, Box::new(|cc| {
let gl = cc.gl.as_ref().ok_or("OpenGL context missing")?;
// SAFETY: eframe owns a current GL context during creation.
let renderer = unsafe { gl.get_parameter_string(eframe::glow::RENDERER) };
if ["llvmpipe","softpipe","swrast","swiftshader"].iter().any(|s| renderer.to_lowercase().contains(s)) { return Err("Software renderer rejected".into()); }
eprintln!("GPU_RENDERER={renderer}");
cc.egui_ctx.set_pixels_per_point(2.0);
let mut style = (*cc.egui_ctx.style()).clone();
style.visuals = egui::Visuals::dark(); style.visuals.override_text_color = Some(egui::Color32::from_rgb(237,241,245));
style.spacing.item_spacing = egui::vec2(12.,12.);
style.text_styles.insert(egui::TextStyle::Body,egui::FontId::proportional(18.));
style.text_styles.insert(egui::TextStyle::Button,egui::FontId::proportional(18.));
cc.egui_ctx.set_style(style);
Ok(Box::new(Shell { page: Page::Home, notes: String::new(), renderer }))
}))
}
#[test]
fn clock_wraps_days() { assert_eq!(clock(0),"00:00"); assert_eq!(clock(86399),"23:59"); assert_eq!(clock(86400),"00:00"); }