mirror of
https://github.com/Death916/deathclock.git
synced 2026-04-10 03:04:40 -07:00
simplify enums and test into libcosmic
This commit is contained in:
parent
c3923a984e
commit
b3b2b6f200
16 changed files with 7619 additions and 72 deletions
109
rust/src/main.rs
109
rust/src/main.rs
|
|
@ -1,93 +1,62 @@
|
||||||
use iced;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
struct State {
|
#[derive(Debug)]
|
||||||
current_time: chrono::DateTime<chrono::Utc>,
|
enum Sport {
|
||||||
next_alarm: Option<chrono::DateTime<chrono::Utc>>,
|
NBA,
|
||||||
news: Vec<String>,
|
NFL,
|
||||||
weather: String,
|
MLB,
|
||||||
location: String,
|
|
||||||
scores: Vec<Scores>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum League {
|
struct Game {
|
||||||
NBA(NbaScores),
|
sport: Sport,
|
||||||
NFL(Vec<String>),
|
|
||||||
MLB(Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Scores {
|
|
||||||
nba: League,
|
|
||||||
nfl: League,
|
|
||||||
mlb: League,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct NbaScores {
|
|
||||||
teams: Vec<String>,
|
|
||||||
team1: String,
|
team1: String,
|
||||||
team2: String,
|
team2: String,
|
||||||
score1: String,
|
score1: String,
|
||||||
score2: String,
|
score2: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scores {
|
impl Game {
|
||||||
fn new() -> Self {
|
fn new(sport: Sport, team1: &str, team2: &str, score1: &str, score2: &str) -> Self {
|
||||||
Scores {
|
Game {
|
||||||
nba: League::NBA(NbaScores {
|
sport,
|
||||||
teams: Vec::new(),
|
team1: team1.to_string(),
|
||||||
team1: String::new(),
|
team2: team2.to_string(),
|
||||||
team2: String::new(),
|
score1: score1.to_string(),
|
||||||
score1: String::new(),
|
score2: score2.to_string(),
|
||||||
score2: String::new(),
|
|
||||||
}),
|
|
||||||
nfl: League::NFL(Vec::new()),
|
|
||||||
mlb: League::MLB(Vec::new()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut scores = Scores::new();
|
struct State {
|
||||||
|
current_time: chrono::DateTime<chrono::Utc>,
|
||||||
|
next_alarm: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
news: Vec<String>,
|
||||||
|
weather: String,
|
||||||
|
location: String,
|
||||||
|
scores: Vec<Game>,
|
||||||
|
}
|
||||||
|
|
||||||
let state = State {
|
let mut state = State {
|
||||||
current_time: chrono::Utc::now(),
|
current_time: chrono::Utc::now(),
|
||||||
next_alarm: None,
|
next_alarm: Some(chrono::Utc::now() + chrono::Duration::hours(1)),
|
||||||
news: Vec::new(),
|
news: vec!["Breaking news!".to_string()],
|
||||||
weather: String::new(),
|
weather: "Sunny".to_string(),
|
||||||
location: String::new(),
|
location: "Sacramento".to_string(),
|
||||||
scores: Vec::new(),
|
scores: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = state;
|
state.scores.push(Game::new(Sport::NBA, "Lakers", "Warriors", "100", "95"));
|
||||||
|
state.scores.push(Game::new(Sport::NBA, "Celtics", "Nets", "110", "105"));
|
||||||
state.current_time = chrono::Utc::now();
|
state.scores.push(Game::new(Sport::MLB, "Red Sox", "Yankees", "100", "95"));
|
||||||
|
|
||||||
state.next_alarm = Some(chrono::Utc::now() + chrono::Duration::hours(1));
|
|
||||||
|
|
||||||
state.news.push("Breaking news!".to_string());
|
|
||||||
|
|
||||||
state.weather = "Sunny".to_string();
|
|
||||||
|
|
||||||
state.location = "Sacramento".to_string();
|
|
||||||
|
|
||||||
scores.nba = League::NBA(NbaScores {
|
|
||||||
teams: vec![
|
|
||||||
"Team A vs Team B".to_string(),
|
|
||||||
"Team C vs Team D".to_string(),
|
|
||||||
],
|
|
||||||
team1: "Team A".to_string(),
|
|
||||||
team2: "Team B".to_string(),
|
|
||||||
score1: "100".to_string(),
|
|
||||||
score2: "95".to_string(),
|
|
||||||
});
|
|
||||||
state.scores.push(scores);
|
|
||||||
|
|
||||||
|
|
||||||
println!("{:?}", state.current_time);
|
println!("{:?}", state.current_time);
|
||||||
println!("{:?}", state.scores);
|
println!("---------------");
|
||||||
println!("{:?}", state.scores[0].nba);
|
|
||||||
|
|
||||||
|
|
||||||
|
for game in &state.scores {
|
||||||
|
println!("+----------------------+");
|
||||||
|
println!("| Sport: {:?}", game.sport);
|
||||||
|
println!("| {} vs {}", game.team1, game.team2);
|
||||||
|
println!("| {} - {}", game.score1, game.score2);
|
||||||
|
println!("+----------------------+");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
rustclock/.gitignore
vendored
Normal file
14
rustclock/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
.cargo/
|
||||||
|
*.pdb
|
||||||
|
**/*.rs.bk
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
vendor/
|
||||||
|
vendor.tar
|
||||||
|
debian/*
|
||||||
|
!debian/changelog
|
||||||
|
!debian/control
|
||||||
|
!debian/copyright
|
||||||
|
!debian/install
|
||||||
|
!debian/rules
|
||||||
|
!debian/source
|
||||||
6901
rustclock/Cargo.lock
generated
Normal file
6901
rustclock/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
49
rustclock/Cargo.toml
Normal file
49
rustclock/Cargo.toml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
[package]
|
||||||
|
name = "rustclock"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "none"
|
||||||
|
description = "deathclock in rust"
|
||||||
|
repository = "https://github.com/death916/deathclock"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = "0.4.44"
|
||||||
|
futures-util = "0.3.31"
|
||||||
|
i18n-embed = { version = "0.16", features = [
|
||||||
|
"fluent-system",
|
||||||
|
"desktop-requester",
|
||||||
|
] }
|
||||||
|
i18n-embed-fl = "0.10"
|
||||||
|
open = "5.3.2"
|
||||||
|
rust-embed = "8.8.0"
|
||||||
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
|
|
||||||
|
[dependencies.libcosmic]
|
||||||
|
git = "https://github.com/pop-os/libcosmic.git"
|
||||||
|
# See https://github.com/pop-os/libcosmic/blob/master/Cargo.toml for available features.
|
||||||
|
features = [
|
||||||
|
# Accessibility support
|
||||||
|
"a11y",
|
||||||
|
# About widget for the app
|
||||||
|
"about",
|
||||||
|
# Uses cosmic-settings-daemon to watch for config file changes
|
||||||
|
"dbus-config",
|
||||||
|
# Support creating additional application windows.
|
||||||
|
"multi-window",
|
||||||
|
# On app startup, focuses an existing instance if the app is already open
|
||||||
|
"single-instance",
|
||||||
|
# Uses tokio as the executor for the runtime
|
||||||
|
"tokio",
|
||||||
|
# Windowing support for X11, Windows, Mac, & Redox
|
||||||
|
"winit",
|
||||||
|
# Add Wayland support to winit
|
||||||
|
"wayland",
|
||||||
|
# GPU-accelerated rendering
|
||||||
|
"wgpu",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Uncomment to test a locally-cloned libcosmic
|
||||||
|
# [patch.'https://github.com/pop-os/libcosmic']
|
||||||
|
# libcosmic = { path = "../libcosmic" }
|
||||||
|
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
|
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||||
17
rustclock/README.md
Normal file
17
rustclock/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Rustclock
|
||||||
|
|
||||||
|
deathclock in rust
|
||||||
|
|
||||||
|
## Translators
|
||||||
|
|
||||||
|
[Fluent][fluent] is used for localization of the software. Fluent's translation files are found in the [i18n directory](./i18n). New translations may copy the [English (en) localization](./i18n/en) of the project, rename `en` to the desired [ISO 639-1 language code][iso-codes], and then translations can be provided for each [message identifier][fluent-guide]. If no translation is necessary, the message may be omitted.
|
||||||
|
|
||||||
|
|
||||||
|
[fluent]: https://projectfluent.org/
|
||||||
|
[fluent-guide]: https://projectfluent.org/fluent/guide/hello.html
|
||||||
|
[iso-codes]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||||
|
[just]: https://github.com/casey/just
|
||||||
|
[rustup]: https://rustup.rs/
|
||||||
|
[rust-analyzer]: https://rust-analyzer.github.io/
|
||||||
|
[mold]: https://github.com/rui314/mold
|
||||||
|
[sccache]: https://github.com/mozilla/sccache
|
||||||
4
rustclock/i18n.toml
Normal file
4
rustclock/i18n.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fallback_language = "en"
|
||||||
|
|
||||||
|
[fluent]
|
||||||
|
assets_dir = "i18n"
|
||||||
7
rustclock/i18n/en/rustclock.ftl
Normal file
7
rustclock/i18n/en/rustclock.ftl
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
app-title = Rustclock
|
||||||
|
about = About
|
||||||
|
repository = Repository
|
||||||
|
view = View
|
||||||
|
welcome = Welcome to COSMIC! ✨
|
||||||
|
page-id = Page { $num }
|
||||||
|
git-description = Git commit {$hash} on {$date}
|
||||||
11
rustclock/resources/app.desktop
Normal file
11
rustclock/resources/app.desktop
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Rustclock
|
||||||
|
Comment=deathclock in rust
|
||||||
|
Type=Application
|
||||||
|
Icon=com.github.pop-os.cosmic-app-template
|
||||||
|
Exec=rustclock %F
|
||||||
|
Terminal=false
|
||||||
|
StartupNotify=true
|
||||||
|
Categories=COSMIC
|
||||||
|
Keywords=COSMIC
|
||||||
|
MimeType=
|
||||||
35
rustclock/resources/app.metainfo.xml
Normal file
35
rustclock/resources/app.metainfo.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>com.github.pop-os.cosmic-app-template</id>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>none</project_license>
|
||||||
|
<name>Rustclock</name>
|
||||||
|
<summary>deathclock in rust</summary>
|
||||||
|
<icon type="remote" width="64" height="64" scale="1">
|
||||||
|
https://github.com/death916/deathclock/raw/main/resources/icons/hicolor/scalable/apps/icon.svg
|
||||||
|
</icon>
|
||||||
|
<url type="vcs-browser">https://github.com/death916/deathclock</url>
|
||||||
|
<launchable type="desktop-id">com.github.pop-os.cosmic-app-template.desktop</launchable>
|
||||||
|
<provides>
|
||||||
|
<id>com.github.pop-os.cosmic-app-template</id>
|
||||||
|
<binaries>
|
||||||
|
<binary>rustclock</binary>
|
||||||
|
</binaries>
|
||||||
|
</provides>
|
||||||
|
<requires>
|
||||||
|
<display_length compare="ge">360</display_length>
|
||||||
|
</requires>
|
||||||
|
<supports>
|
||||||
|
<control>keyboard</control>
|
||||||
|
<control>pointing</control>
|
||||||
|
<control>touch</control>
|
||||||
|
</supports>
|
||||||
|
<categories>
|
||||||
|
<category>COSMIC</category>
|
||||||
|
</categories>
|
||||||
|
<keywords>
|
||||||
|
<keyword>COSMIC</keyword>
|
||||||
|
</keywords>
|
||||||
|
<content_rating type="oars-1.1" />
|
||||||
|
</component>
|
||||||
|
|
||||||
2
rustclock/resources/icons/hicolor/scalable/apps/icon.svg
Normal file
2
rustclock/resources/icons/hicolor/scalable/apps/icon.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"/>
|
||||||
|
After Width: | Height: | Size: 102 B |
389
rustclock/src/app.rs
Normal file
389
rustclock/src/app.rs
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::fl;
|
||||||
|
use crate::sports;
|
||||||
|
use cosmic::app::context_drawer;
|
||||||
|
use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
||||||
|
use cosmic::iced::alignment::{Horizontal, Vertical};
|
||||||
|
use cosmic::iced::{Alignment, Length, Subscription};
|
||||||
|
use cosmic::widget::{self, about::About, icon, menu, nav_bar};
|
||||||
|
use cosmic::{iced_futures, prelude::*};
|
||||||
|
use futures_util::SinkExt;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
||||||
|
const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
|
||||||
|
const APP_ICON: &[u8] = include_bytes!("../resources/icons/hicolor/scalable/apps/icon.svg");
|
||||||
|
|
||||||
|
/// The application model stores app-specific state used to describe its interface and
|
||||||
|
/// drive its logic.
|
||||||
|
pub struct AppModel {
|
||||||
|
/// Application state which is managed by the COSMIC runtime.
|
||||||
|
core: cosmic::Core,
|
||||||
|
/// Display a context drawer with the designated page if defined.
|
||||||
|
context_page: ContextPage,
|
||||||
|
/// The about page for this app.
|
||||||
|
about: About,
|
||||||
|
/// Contains items assigned to the nav bar panel.
|
||||||
|
nav: nav_bar::Model,
|
||||||
|
/// Key bindings for the application's menu bar.
|
||||||
|
key_binds: HashMap<menu::KeyBind, MenuAction>,
|
||||||
|
/// Configuration data that persists between application runs.
|
||||||
|
config: Config,
|
||||||
|
/// Time active
|
||||||
|
time: u32,
|
||||||
|
/// Toggle the watch subscription
|
||||||
|
watch_is_active: bool,
|
||||||
|
// from deathclock
|
||||||
|
|
||||||
|
scores: Vec<sports::Game>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages emitted by the application and its widgets.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
LaunchUrl(String),
|
||||||
|
ToggleContextPage(ContextPage),
|
||||||
|
ToggleWatch,
|
||||||
|
UpdateConfig(Config),
|
||||||
|
WatchTick(u32),
|
||||||
|
// UpdateTime(DateTime<Utc>),
|
||||||
|
// UpdateScores(Scores)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a COSMIC application from the app model
|
||||||
|
impl cosmic::Application for AppModel {
|
||||||
|
/// The async executor that will be used to run your application's commands.
|
||||||
|
type Executor = cosmic::executor::Default;
|
||||||
|
|
||||||
|
/// Data that your application receives to its init method.
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
/// Messages which the application and its widgets will emit.
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
/// Unique identifier in RDNN (reverse domain name notation) format.
|
||||||
|
const APP_ID: &'static str = "dev.mmurphy.Test";
|
||||||
|
|
||||||
|
fn core(&self) -> &cosmic::Core {
|
||||||
|
&self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
fn core_mut(&mut self) -> &mut cosmic::Core {
|
||||||
|
&mut self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the application with any given flags and startup commands.
|
||||||
|
fn init(
|
||||||
|
core: cosmic::Core,
|
||||||
|
_flags: Self::Flags,
|
||||||
|
) -> (Self, Task<cosmic::Action<Self::Message>>) {
|
||||||
|
// Create a nav bar with three page items.
|
||||||
|
let mut nav = nav_bar::Model::default();
|
||||||
|
|
||||||
|
nav.insert()
|
||||||
|
.text(fl!("page-id", num = 1))
|
||||||
|
.data::<Page>(Page::Page1)
|
||||||
|
.icon(icon::from_name("applications-science-symbolic"))
|
||||||
|
.activate();
|
||||||
|
|
||||||
|
nav.insert()
|
||||||
|
.text(fl!("page-id", num = 2))
|
||||||
|
.data::<Page>(Page::Page2)
|
||||||
|
.icon(icon::from_name("applications-system-symbolic"));
|
||||||
|
|
||||||
|
nav.insert()
|
||||||
|
.text(fl!("page-id", num = 3))
|
||||||
|
.data::<Page>(Page::Page3)
|
||||||
|
.icon(icon::from_name("applications-games-symbolic"));
|
||||||
|
|
||||||
|
// Create the about widget
|
||||||
|
let about = About::default()
|
||||||
|
.name(fl!("app-title"))
|
||||||
|
.icon(widget::icon::from_svg_bytes(APP_ICON))
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.links([(fl!("repository"), REPOSITORY)])
|
||||||
|
.license(env!("CARGO_PKG_LICENSE"));
|
||||||
|
|
||||||
|
// Construct the app model with the runtime's core.
|
||||||
|
let mut app = AppModel {
|
||||||
|
core,
|
||||||
|
context_page: ContextPage::default(),
|
||||||
|
about,
|
||||||
|
nav,
|
||||||
|
key_binds: HashMap::new(),
|
||||||
|
scores: sports::sample_scores(),
|
||||||
|
|
||||||
|
|
||||||
|
// Optional configuration file for an application.
|
||||||
|
config: cosmic_config::Config::new(Self::APP_ID, Config::VERSION)
|
||||||
|
.map(|context| match Config::get_entry(&context) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err((_errors, config)) => {
|
||||||
|
// for why in errors {
|
||||||
|
// tracing::error!(%why, "error loading app config");
|
||||||
|
// }
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
time: 0,
|
||||||
|
watch_is_active: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a startup command that sets the window title.
|
||||||
|
let command = app.update_title();
|
||||||
|
|
||||||
|
(app, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Elements to pack at the start of the header bar.
|
||||||
|
fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
|
||||||
|
let menu_bar = menu::bar(vec![menu::Tree::with_children(
|
||||||
|
menu::root(fl!("view")).apply(Element::from),
|
||||||
|
menu::items(
|
||||||
|
&self.key_binds,
|
||||||
|
vec![menu::Item::Button(fl!("about"), None, MenuAction::About)],
|
||||||
|
),
|
||||||
|
)]);
|
||||||
|
|
||||||
|
vec![menu_bar.into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables the COSMIC application to create a nav bar with this model.
|
||||||
|
fn nav_model(&self) -> Option<&nav_bar::Model> {
|
||||||
|
Some(&self.nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display a context drawer if the context page is requested.
|
||||||
|
fn context_drawer(&self) -> Option<context_drawer::ContextDrawer<'_, Self::Message>> {
|
||||||
|
if !self.core.window.show_context {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match self.context_page {
|
||||||
|
ContextPage::About => context_drawer::about(
|
||||||
|
&self.about,
|
||||||
|
|url| Message::LaunchUrl(url.to_string()),
|
||||||
|
Message::ToggleContextPage(ContextPage::About),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the interface based on the current state of the application model.
|
||||||
|
///
|
||||||
|
/// Application events will be processed through the view. Any messages emitted by
|
||||||
|
/// events received by widgets will be passed to the update method.
|
||||||
|
fn view(&self) -> Element<'_, Self::Message> {
|
||||||
|
let space_s = cosmic::theme::spacing().space_s;
|
||||||
|
let content: Element<_> = match self.nav.active_data::<Page>().unwrap() {
|
||||||
|
Page::Page1 => {
|
||||||
|
let header = widget::row::with_capacity(2)
|
||||||
|
.push(widget::text::title1(fl!("welcome")))
|
||||||
|
.push(widget::text::title3(fl!("page-id", num = 1)))
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.spacing(space_s);
|
||||||
|
|
||||||
|
let counter_label = ["Watch: ", self.time.to_string().as_str()].concat();
|
||||||
|
let section = cosmic::widget::settings::section().add(
|
||||||
|
cosmic::widget::settings::item::builder(counter_label).control(
|
||||||
|
widget::button::text(if self.watch_is_active {
|
||||||
|
"Stop"
|
||||||
|
} else {
|
||||||
|
"Start"
|
||||||
|
})
|
||||||
|
.on_press(Message::ToggleWatch),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
widget::column::with_capacity(2)
|
||||||
|
.push(header)
|
||||||
|
.push(section)
|
||||||
|
.spacing(space_s)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
Page::Page2 => {
|
||||||
|
let game = &self.scores[0];
|
||||||
|
let score_text = format!(
|
||||||
|
"{} {} - {} {}",
|
||||||
|
game.team1, game.score1, game.score2, game.team2
|
||||||
|
);
|
||||||
|
let header = widget::row::with_capacity(2)
|
||||||
|
.push(widget::text::title1(fl!("welcome")))
|
||||||
|
.push(widget::text::body("hello_world"))
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.spacing(space_s);
|
||||||
|
|
||||||
|
widget::column::with_capacity(2)
|
||||||
|
.push(header)
|
||||||
|
.push(widget::text::body(score_text))
|
||||||
|
.spacing(space_s)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
Page::Page3 => {
|
||||||
|
let header = widget::row::with_capacity(2)
|
||||||
|
.push(widget::text::title1(fl!("welcome")))
|
||||||
|
.push(widget::text::title3(fl!("page-id", num = 3)))
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.spacing(space_s);
|
||||||
|
|
||||||
|
widget::column::with_capacity(1)
|
||||||
|
.push(header)
|
||||||
|
.spacing(space_s)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
widget::container(content)
|
||||||
|
.width(600)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.apply(widget::container)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_x(Horizontal::Center)
|
||||||
|
.align_y(Vertical::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register subscriptions for this application.
|
||||||
|
///
|
||||||
|
/// Subscriptions are long-running async tasks running in the background which
|
||||||
|
/// emit messages to the application through a channel. They can be dynamically
|
||||||
|
/// stopped and started conditionally based on application state, or persist
|
||||||
|
/// indefinitely.
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
// Add subscriptions which are always active.
|
||||||
|
let mut subscriptions = vec![
|
||||||
|
// Watch for application configuration changes.
|
||||||
|
self.core()
|
||||||
|
.watch_config::<Config>(Self::APP_ID)
|
||||||
|
.map(|update| {
|
||||||
|
// for why in update.errors {
|
||||||
|
// tracing::error!(?why, "app config error");
|
||||||
|
// }
|
||||||
|
|
||||||
|
Message::UpdateConfig(update.config)
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Conditionally enables a timer that emits a message every second.
|
||||||
|
if self.watch_is_active {
|
||||||
|
subscriptions.push(Subscription::run(|| {
|
||||||
|
iced_futures::stream::channel(1, |mut emitter| async move {
|
||||||
|
let mut time = 1;
|
||||||
|
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
_ = emitter.send(Message::WatchTick(time)).await;
|
||||||
|
time += 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Subscription::batch(subscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles messages emitted by the application and its widgets.
|
||||||
|
///
|
||||||
|
/// Tasks may be returned for asynchronous execution of code in the background
|
||||||
|
/// on the application's async runtime.
|
||||||
|
fn update(&mut self, message: Self::Message) -> Task<cosmic::Action<Self::Message>> {
|
||||||
|
match message {
|
||||||
|
Message::WatchTick(time) => {
|
||||||
|
self.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::ToggleWatch => {
|
||||||
|
self.watch_is_active = !self.watch_is_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::ToggleContextPage(context_page) => {
|
||||||
|
if self.context_page == context_page {
|
||||||
|
// Close the context drawer if the toggled context page is the same.
|
||||||
|
self.core.window.show_context = !self.core.window.show_context;
|
||||||
|
} else {
|
||||||
|
// Open the context drawer to display the requested context page.
|
||||||
|
self.context_page = context_page;
|
||||||
|
self.core.window.show_context = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::UpdateConfig(config) => {
|
||||||
|
self.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::LaunchUrl(url) => match open::that_detached(&url) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("failed to open {url:?}: {err}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a nav item is selected.
|
||||||
|
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<cosmic::Action<Self::Message>> {
|
||||||
|
// Activate the page in the model.
|
||||||
|
self.nav.activate(id);
|
||||||
|
|
||||||
|
self.update_title()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppModel {
|
||||||
|
/// Updates the header and window titles.
|
||||||
|
pub fn update_title(&mut self) -> Task<cosmic::Action<Message>> {
|
||||||
|
let mut window_title = fl!("app-title");
|
||||||
|
|
||||||
|
if let Some(page) = self.nav.text(self.nav.active()) {
|
||||||
|
window_title.push_str(" — ");
|
||||||
|
window_title.push_str(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(id) = self.core.main_window_id() {
|
||||||
|
self.set_window_title(window_title, id)
|
||||||
|
} else {
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The page to display in the application.
|
||||||
|
pub enum Page {
|
||||||
|
Page1,
|
||||||
|
Page2,
|
||||||
|
Page3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The context page to display in the context drawer.
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub enum ContextPage {
|
||||||
|
#[default]
|
||||||
|
About,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MenuAction {
|
||||||
|
About,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl menu::action::MenuAction for MenuAction {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn message(&self) -> Self::Message {
|
||||||
|
match self {
|
||||||
|
MenuAction::About => Message::ToggleContextPage(ContextPage::About),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
rustclock/src/config.rs
Normal file
9
rustclock/src/config.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// SPDX-License-Identifier: none
|
||||||
|
|
||||||
|
use cosmic::cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, CosmicConfigEntry, Eq, PartialEq)]
|
||||||
|
#[version = 1]
|
||||||
|
pub struct Config {
|
||||||
|
demo: String,
|
||||||
|
}
|
||||||
52
rustclock/src/i18n.rs
Normal file
52
rustclock/src/i18n.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// SPDX-License-Identifier: none
|
||||||
|
|
||||||
|
//! Provides localization support for this crate.
|
||||||
|
|
||||||
|
use i18n_embed::{
|
||||||
|
DefaultLocalizer, LanguageLoader, Localizer,
|
||||||
|
fluent::{FluentLanguageLoader, fluent_language_loader},
|
||||||
|
unic_langid::LanguageIdentifier,
|
||||||
|
};
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
/// Applies the requested language(s) to requested translations from the `fl!()` macro.
|
||||||
|
pub fn init(requested_languages: &[LanguageIdentifier]) {
|
||||||
|
if let Err(why) = localizer().select(requested_languages) {
|
||||||
|
eprintln!("error while loading fluent localizations: {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the `Localizer` to be used for localizing this library.
|
||||||
|
#[must_use]
|
||||||
|
pub fn localizer() -> Box<dyn Localizer> {
|
||||||
|
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "i18n/"]
|
||||||
|
struct Localizations;
|
||||||
|
|
||||||
|
pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| {
|
||||||
|
let loader: FluentLanguageLoader = fluent_language_loader!();
|
||||||
|
|
||||||
|
loader
|
||||||
|
.load_fallback_language(&Localizations)
|
||||||
|
.expect("Error while loading fallback language");
|
||||||
|
|
||||||
|
loader
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/// Request a localized string by ID from the i18n/ directory.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! fl {
|
||||||
|
($message_id:literal) => {{
|
||||||
|
i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id)
|
||||||
|
}};
|
||||||
|
|
||||||
|
($message_id:literal, $($args:expr),*) => {{
|
||||||
|
i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
24
rustclock/src/main.rs
Normal file
24
rustclock/src/main.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-License-Identifier: none
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
mod config;
|
||||||
|
mod i18n;
|
||||||
|
mod sports;
|
||||||
|
|
||||||
|
fn main() -> cosmic::iced::Result {
|
||||||
|
// Get the system's preferred languages.
|
||||||
|
let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
|
||||||
|
|
||||||
|
// Enable localizations to be applied.
|
||||||
|
i18n::init(&requested_languages);
|
||||||
|
|
||||||
|
// Settings for configuring the application window and iced runtime.
|
||||||
|
let settings = cosmic::app::Settings::default().size_limits(
|
||||||
|
cosmic::iced::Limits::NONE
|
||||||
|
.min_width(360.0)
|
||||||
|
.min_height(180.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Starts the application's event loop with `()` as the application's flags.
|
||||||
|
cosmic::app::run::<app::AppModel>(settings, ())
|
||||||
|
}
|
||||||
35
rustclock/src/sports.rs
Normal file
35
rustclock/src/sports.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Sport {
|
||||||
|
NBA,
|
||||||
|
NFL,
|
||||||
|
MLB,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Game {
|
||||||
|
pub sport: Sport,
|
||||||
|
pub team1: String,
|
||||||
|
pub team2: String,
|
||||||
|
pub score1: String,
|
||||||
|
pub score2: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Game {
|
||||||
|
pub fn new(sport: Sport, team1: &str, team2: &str, score1: &str, score2: &str) -> Self {
|
||||||
|
Game {
|
||||||
|
sport,
|
||||||
|
team1: team1.to_string(),
|
||||||
|
team2: team2.to_string(),
|
||||||
|
score1: score1.to_string(),
|
||||||
|
score2: score2.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_scores() -> Vec<Game> {
|
||||||
|
vec![
|
||||||
|
Game::new(Sport::NBA, "Lakers", "Warriors", "100", "95"),
|
||||||
|
Game::new(Sport::NBA, "Celtics", "Nets", "110", "105"),
|
||||||
|
Game::new(Sport::MLB, "Red Sox", "Yankees", "100", "95"),
|
||||||
|
]
|
||||||
|
}
|
||||||
31
shell.nix
31
shell.nix
|
|
@ -9,8 +9,37 @@ pkgs.mkShell {
|
||||||
python313Packages.ninja
|
python313Packages.ninja
|
||||||
python313Packages.numpy
|
python313Packages.numpy
|
||||||
bun
|
bun
|
||||||
|
unstable.rustc
|
||||||
|
unstable.cargo
|
||||||
|
unstable.rust-analyzer
|
||||||
|
unstable.rustfmt
|
||||||
|
pkgs.pkg-config
|
||||||
|
pkgs.openssl
|
||||||
|
pkgs.libxcb
|
||||||
|
pkgs.pkg-config
|
||||||
|
pkgs.openssl
|
||||||
|
pkgs.alsa-lib
|
||||||
|
pkgs.libxcb
|
||||||
|
pkgs.wayland
|
||||||
|
pkgs.libxkbcommon
|
||||||
|
pkgs.fontconfig
|
||||||
|
pkgs.freetype
|
||||||
|
pkgs.mesa
|
||||||
|
pkgs.libGL
|
||||||
|
pkgs.glib
|
||||||
|
pkgs.vulkan-loader
|
||||||
|
pkgs.vulkan-headers
|
||||||
|
pkgs.clippy
|
||||||
];
|
];
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||||
|
pkgs.wayland
|
||||||
|
pkgs.libxkbcommon
|
||||||
|
pkgs.mesa
|
||||||
|
pkgs.glib
|
||||||
|
pkgs.vulkan-loader
|
||||||
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
# export PATH="${pkgs.bun}/bin:$PATH"
|
# export PATH="${pkgs.bun}/bin:$PATH"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue