Skip to content

Commit

Permalink
Merge pull request #1 from ahoneybun/add-menu
Browse files Browse the repository at this point in the history
Add menu
  • Loading branch information
ahoneybun authored May 18, 2024
2 parents a6cea95 + 2a86152 commit 61c045a
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 36 deletions.
Binary file removed COSMIC-Backups-PoC.png
Binary file not shown.
Binary file added COSMIC-Backups.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ version = "0.1.0"
edition = "2021"
license = "GPL-3.0"

[build-dependencies]
vergen = { version = "8", features = ["git", "gitcl"] }

[dependencies]
i18n-embed-fl = "0.8"
once_cell = "1.19.0"
rust-embed = "8.3.0"
log = "0.4"
open = "5.0.2"

[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

The current plan is to use Restic as the backend but for now the UI/UX is still being worked on. I have no idea if this will move forward past the init stage.

![COSMIC Backups PoC](COSMIC-Backups-PoC.png)
![COSMIC Backups](COSMIC-Backups.png)

## Install

Expand Down
17 changes: 17 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Rebuild if i18n files change
println!("cargo:rerun-if-changed=i18n");

// Emit version information (if not cached by just vendor)
let mut vergen = vergen::EmitBuilder::builder();
println!("cargo:rerun-if-env-changed=VERGEN_GIT_COMMIT_DATE");
if std::env::var_os("VERGEN_GIT_COMMIT_DATE").is_none() {
vergen.git_commit_date();
}
println!("cargo:rerun-if-env-changed=VERGEN_GIT_SHA");
if std::env::var_os("VERGEN_GIT_SHA").is_none() {
vergen.git_sha(false);
}
vergen.fail_on_error().emit()?;
Ok(())
}
21 changes: 21 additions & 0 deletions i18n/en/cosmic_backups.ftl
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
cosmic-backups = COSMIC Backups
app-title = COSMIC Backups
welcome = Backup and backup often! ✨
# Context Pages

## About
git-description = Git commit {$hash} on {$date}
# Menu

## File
file = File
new-window = New window
quit = Quit
## Edit
edit = Edit
cut = Cut
## View
view = View
menu-about = About COSMIC Backups...
218 changes: 185 additions & 33 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,125 @@
// SPDX-License-Identifier: GPL-3.0-only

use std::{
collections::{HashMap},
env,
process,
};
use crate::fl;
use cosmic::app::{Command, Core};
use cosmic::{
cosmic_theme, ApplicationExt,
iced::{Alignment, Length},
};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::Length;
use cosmic::iced::window;
use cosmic::iced_core::keyboard::Key;
use cosmic::widget::menu::{
action::{MenuAction},
key_bind::{KeyBind, Modifier},
};
use cosmic::widget::segmented_button::Entity;
use cosmic::{widget, Application, Element};

pub mod menu;

/// This is the struct that represents your application.
/// It is used to define the data that will be used by your application.
#[derive(Clone, Default)]
pub struct CosmicBackups {
#[derive(Clone)]
pub struct App {
/// This is the core of your application, it is used to communicate with the Cosmic runtime.
/// It is used to send messages to your application, and to access the resources of the Cosmic runtime.
core: Core,
context_page: ContextPage,
key_binds: HashMap<KeyBind, Action>,
}

/// This is the enum that contains all the possible variants that your application will need to transmit messages.
/// This is used to communicate between the different parts of your application.
/// If your application does not need to send messages, you can use an empty enum or `()`.
#[derive(Debug, Clone)]
pub enum Message {}

/// Implement the `Application` trait for your application.
/// This is where you define the behavior of your application.
///
/// The `Application` trait requires you to define the following types and constants:
/// - `Executor` is the executor that will be used to run your application.
/// - `Flags` is the data that your application needs to use before it starts.
/// - `Message` is the enum that contains all the possible variants that your application will need to transmit messages.
/// - `APP_ID` is the unique identifier of your application.
impl Application for CosmicBackups {
pub enum Message {
// Cut(Option<Entity>),
ToggleContextPage(ContextPage),
LaunchUrl(String),
WindowClose,
WindowNew,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ContextPage {
About,
}

impl ContextPage {
fn title(&self) -> String {
match self {
Self::About => String::new(),
}
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action {
About,
// Cut,
WindowClose,
WindowNew,
}

impl MenuAction for Action {
type Message = Message;
fn message(&self, _entity_opt: Option<Entity>) -> Self::Message {
match self {
Action::About => Message::ToggleContextPage(ContextPage::About),
// Action::Cut => Message::Cut(entity_opt),
Action::WindowClose => Message::WindowClose,
Action::WindowNew => Message::WindowNew,
}
}
}

impl App {
// fn update_config(&mut self) -> Command<CosmicMessage<Message>> {
// cosmic::app::command::set_theme(self.config.app_theme.theme())
// }

fn about(&self) -> Element<Message> {
let cosmic_theme::Spacing { space_xxs, .. } = cosmic::theme::active().cosmic().spacing;
let repository = "https://github.com/ahoneybun/cosmic-backups";
let hash = env!("VERGEN_GIT_SHA");
let short_hash: String = hash.chars().take(7).collect();
let date = env!("VERGEN_GIT_COMMIT_DATE");
widget::column::with_children(vec![
widget::svg(widget::svg::Handle::from_memory(
&include_bytes!(
"../res/icons/hicolor/128x128/apps/com.example.CosmicAppTemplate.svg"
)[..],
))
.into(),
widget::text::title3(fl!("cosmic-backups")).into(),
widget::button::link(repository)
.on_press(Message::LaunchUrl(repository.to_string()))
.padding(0)
.into(),
widget::button::link(fl!(
"git-description",
hash = short_hash.as_str(),
date = date
))
.on_press(Message::LaunchUrl(format!("{}/commits/{}", repository, hash)))
.padding(0)
.into(),
])
.align_items(Alignment::Center)
.spacing(space_xxs)
.into()
}

}


impl Application for App {
type Executor = cosmic::executor::Default;

type Flags = ();
Expand All @@ -47,29 +137,30 @@ impl Application for CosmicBackups {
}

/// This is the header of your application, it can be used to display the title of your application.
fn header_center(&self) -> Vec<Element<Self::Message>> {
vec![widget::text::heading(fl!("app-title")).into()]
fn header_start(&self) -> Vec<Element<Self::Message>> {
vec![menu::menu_bar(&self.key_binds)]
}

fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) {
let app = App {
core,
context_page: ContextPage::About,
key_binds: key_binds(),
};

(app, Command::none())
}

/// This is the entry point of your application, it is where you initialize your application.
///
/// Any work that needs to be done before the application starts should be done here.
///
/// - `core` is used to passed on for you by libcosmic to use in the core of your own application.
/// - `flags` is used to pass in any data that your application needs to use before it starts.
/// - `Command` type is used to send messages to your application. `Command::none()` can be used to send no messages to your application.
fn init(core: Core, _flags: Self::Flags) -> (Self, Command<Self::Message>) {
let example = CosmicBackups { core };

(example, Command::none())
fn context_drawer(&self) -> Option<Element<Message>> {
if !self.core.window.show_context {
return None;
}

Some(match self.context_page {
ContextPage::About => self.about(),
})
}

/// This is the main view of your application, it is the root of your widget tree.
///
/// The `Element` type is used to represent the visual elements of your application,
/// it has a `Message` associated with it, which dictates what type of message it can send.
///
/// To get a better sense of which widgets are available, check out the `widget` module.
fn view(&self) -> Element<Self::Message> {
widget::container(widget::text::title1(fl!("welcome")))
.width(Length::Fill)
Expand All @@ -78,4 +169,65 @@ impl Application for CosmicBackups {
.align_y(Vertical::Center)
.into()
}

/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {

match message {
Message::ToggleContextPage(context_page) => {
//TODO: ensure context menus are closed
if self.context_page == context_page {
self.core.window.show_context = !self.core.window.show_context;
} else {
self.context_page = context_page;
self.core.window.show_context = true;
}
self.set_context_title(context_page.title());
}
Message::WindowClose => {
return window::close(window::Id::MAIN);
}
Message::WindowNew => match env::current_exe() {
Ok(exe) => match process::Command::new(&exe).spawn() {
Ok(_child) => {}
Err(err) => {
eprintln!("failed to execute {:?}: {}", exe, err);
}
},
Err(err) => {
eprintln!("failed to get current executable path: {}", err);
}
},
Message::LaunchUrl(url) => match open::that_detached(&url) {
Ok(()) => {}
Err(err) => {
log::warn!("failed to open {:?}: {}", url, err);
}
},
}

Command::none()

}
}

pub fn key_binds() -> HashMap<KeyBind, Action> {
let mut key_binds = HashMap::new();

macro_rules! bind {
([$($modifier:ident),* $(,)?], $key:expr, $action:ident) => {{
key_binds.insert(
KeyBind {
modifiers: vec![$(Modifier::$modifier),*],
key: $key,
},
Action::$action,
);
}};
}

bind!([Ctrl], Key::Character("w".into()), WindowClose);
bind!([Ctrl, Shift], Key::Character("n".into()), WindowNew);

key_binds
}
50 changes: 50 additions & 0 deletions src/app/menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-3.0-only

use std::collections::HashMap;

use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::{
widget::menu::{items, root, Item, ItemHeight, ItemWidth, MenuBar, Tree},
Element,
};

use crate::{
app::{Action, Message},
fl,
};

pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
MenuBar::new(vec![Tree::with_children(
root(fl!("file")),
items(
key_binds,
vec![
Item::Button(fl!("new-window"), Action::WindowNew),
Item::Button(fl!("quit"), Action::WindowClose),
],
),
),
// Tree::with_children(
// root(fl!("edit")),
// items(
// key_binds,
// vec![
// Item::Button(fl!("cut"), Action::Cut),
// ],
// ),
// ),
Tree::with_children(
root(fl!("view")),
items(
key_binds,
vec![
Item::Button(fl!("menu-about"), Action::About),
],
),
),
])
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(240))
.spacing(4.0)
.into()
}
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only

use app::CosmicBackups;
use crate::app::App;
/// The `app` module is used by convention to indicate the main component of our application.
mod app;
mod core;
Expand All @@ -12,5 +12,5 @@ mod core;
/// If your app does not need any flags, you can pass in `()`.
fn main() -> cosmic::iced::Result {
let settings = cosmic::app::Settings::default();
cosmic::app::run::<CosmicBackups>(settings, ())
cosmic::app::run::<App>(settings, ())
}

0 comments on commit 61c045a

Please sign in to comment.