Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rewrite #40

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,773 changes: 1,058 additions & 715 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ edition = "2021"

[dependencies]
serde = "1.0.210"
eframe = { version = "0.29.1", default-features = true, features = ["persistence"] }
egui_extras = "0.29.1"
iced = "0.13.1"
chrono = "0.4.38"
rand = "0.8.5"

# The profile that 'cargo dist' will build with
Expand Down
295 changes: 64 additions & 231 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,251 +1,84 @@
use eframe::egui;
use egui_extras::{TableBuilder, Column};

mod task; // used by task_list
mod task_list;
use task_list::TaskList;

fn main() -> eframe::Result {
let native_options = eframe::NativeOptions::default();
eframe::run_native("STAYFOCUSED", native_options,
Box::new(|cc| Ok(Box::new(MainApp::new(cc)))))
//use iced::widget::{Column, TextInput};
//use iced::widget::{button, column, text, text_input};
use iced::widget::text;
use iced::Element;
use serde::{Deserialize, Serialize};

mod project;
mod project_list;
mod random;
mod task;
mod time_commitment;

pub use project::Project;
pub use project_list::ProjectList;
pub use task::Task;
pub use time_commitment::TimeCommitment;

#[derive(Debug)]
enum Message {
ProjectAdd,
ProjectRemove(usize),
ProjectSetName(usize, String),
ProjectSetDesc(usize, String),
ProjectSetNote(usize, String),
TaskAdd,
TaskRemove(usize),
TaskSetName(usize, String),
TaskSetDesc(usize, String),
TaskSetNote(usize, String),
SwitchView(View),
}

#[derive(Default, serde::Deserialize, serde::Serialize)]
#[derive(Debug, Default, Deserialize, Serialize)]
enum View {
#[default]
ActiveTask,
TaskList,
Task,
Project,
ProjectList,
}

#[derive(Default)]
struct MainApp {
struct App {
projects: ProjectList,
view: View,
tasks: TaskList,
needs_save: bool,
}

impl MainApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
// Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
// Restore app state using cc.storage (requires the "persistence" feature).
// Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
// for e.g. egui::PaintCallback.

use egui::FontFamily::Proportional;
use egui::FontId;
use egui::TextStyle::*;

let mut style = (*cc.egui_ctx.style()).clone();

style.text_styles = [
(Heading, FontId::new(20.0, Proportional)),
(Name("Heading2".into()), FontId::new(20.0, Proportional)),
(Name("Context".into()), FontId::new(20.0, Proportional)),
(Body, FontId::new(20.0, Proportional)),
(Monospace, FontId::new(14.0, Proportional)),
(Button, FontId::new(14.0, Proportional)),
(Small, FontId::new(10.0, Proportional)),
].into();

//style.spacing.item_spacing = egui::vec2(20.0, 20.0);
//style.spacing.button_padding = egui::vec2(10.0, 10.0);
style.spacing.interact_size = egui::vec2(30.0, 30.0);

cc.egui_ctx.set_style(style);


let mut slf = Self::default();

if let Some(storage) = cc.storage {
if let Some(tasks) = eframe::get_value(storage, eframe::APP_KEY) {
slf.tasks = tasks;
}
}

slf
impl App {
fn set_view(&mut self, view: View) {
self.view = view;
}
}

fn add_sized_btn(ui: &mut egui::Ui, name: &str) -> egui::Response {
let button_size = [100.0, 30.0];
ui.add_sized(button_size, egui::Button::new(name))
}

// [ linear clock ]
// [ current task ]
// | notes |
// [Pause][wrap up indicator] [ Next ] [ List ]
fn update_active_task(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let interact_size = ctx.style().spacing.interact_size;

egui::TopBottomPanel::bottom("active_task_bottom").show(ctx, |ui| {
TableBuilder::new(ui)
.column(Column::exact(100.0))
.column(Column::remainder())
.column(Column::exact(100.0))
.column(Column::exact(100.0))
.body(|mut body| {
body.row(interact_size.y, |mut row| {
row.col(|ui| {
if self.tasks.current().is_tracking_time() {
if Self::add_sized_btn(ui, "Pause").clicked() {
self.tasks.current_mut().stop();
}
} else {
if Self::add_sized_btn(ui, "Start").clicked() {
self.tasks.current_mut().start();
}
}
});

row.col(|ui| {
self.tasks.current_mut().tick();
let time_str = self.tasks.current().elapsed_time_str();
ui.label(time_str);
});

row.col(|ui| {
if Self::add_sized_btn(ui, "Next").clicked() && !self.tasks.is_empty() {
self.tasks.choose_random();
}
});

row.col(|ui| {
if Self::add_sized_btn(ui, "Tasks").clicked() {
self.view = View::TaskList;
}
});
});
});
});

egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered_justified(|ui| {
let text = &self.tasks.current().description;

let style = ctx.style();
let mut layout_job = egui::text::LayoutJob::default();
egui::RichText::new(text)
.color(style.visuals.text_color())
.size(40.0)
.append_to(
&mut layout_job,
&style,
egui::FontSelection::Default,
egui::Align::Center,
);

ui.label(layout_job);

egui::ScrollArea::vertical().show(ui, |ui| {
if ui.text_edit_multiline(&mut self.tasks.current_mut().note).changed() {
self.needs_save = true;
}
});
});
});

// If task is running, request a redraw to update the timer.
if self.tasks.current().is_tracking_time() {
ctx.request_repaint_after_secs(0.2);
}
fn update_inner(app: &mut App, message: Message) -> Result<(), Box<dyn std::error::Error>> {
match message {
Message::ProjectAdd => app.projects.push_default(),
Message::ProjectRemove(idx) => app.projects.remove(idx),
Message::ProjectSetName(idx, name) => app.projects.set_name(idx, name)?,
Message::ProjectSetDesc(idx, desc) => app.projects.set_desc(idx, desc)?,
Message::ProjectSetNote(idx, note) => app.projects.set_note(idx, note)?,
Message::TaskAdd => app.projects.current_mut()?.push_default_task(),
Message::TaskRemove(idx) => app.projects.current_mut()?.remove_task(idx),
Message::TaskSetName(idx, name) => app.projects.current_mut()?.set_name(idx, name)?,
Message::TaskSetDesc(idx, desc) => app.projects.current_mut()?.set_desc(idx, desc)?,
Message::TaskSetNote(idx, note) => app.projects.current_mut()?.set_note(idx, note)?,
Message::SwitchView(view) => app.set_view(view),
}

// [ linear clock ]
// Tasks:
// [ pri ] [ duration ] [ task ] [ Edit ] [ Delete ]
// [ Add ]
fn update_task_list(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let interact_size = ctx.style().spacing.interact_size;

egui::TopBottomPanel::bottom("task_list_bottom").show(ctx, |ui| {
TableBuilder::new(ui)
.column(Column::remainder())
.column(Column::exact(100.0))
.column(Column::exact(100.0))
//.header(30.0, |mut header| {})
.body(|mut body| {
body.row(interact_size.y, |mut row| {
row.col(|_ui| { });

row.col(|ui| {
if Self::add_sized_btn(ui, "Add").clicked() {
self.tasks.push_default();
}
});

row.col(|ui| {
if Self::add_sized_btn(ui, "Done").clicked() {
self.view = View::ActiveTask;
}
});
});

});
});

egui::CentralPanel::default().show(ctx, |ui| {
TableBuilder::new(ui)
.striped(true)
.column(Column::remainder())
.column(Column::exact(100.0))
.column(Column::exact(100.0))
.header(interact_size.x, |mut header| {
header.col(|ui| { ui.heading("Task"); });
header.col(|ui| { ui.heading("Elapsed"); });
header.col(|ui| { ui.heading("Actions"); });
})
.body(|mut body| {
let mut deferred_removal: Option<usize> = None;
for (i, task) in self.tasks.iter_mut().enumerate() {
body.row(interact_size.y, |mut row| {
row.col(|ui| {
ui.text_edit_singleline(&mut task.description);
});

row.col(|ui| {
ui.label(task.elapsed_time_str());
});

row.col(|ui| {
if Self::add_sized_btn(ui, "Delete").clicked() {
deferred_removal = Some(i);
}
});
});
}
if let Some(idx) = deferred_removal {
self.tasks.remove(idx)
}
});
});
}
Ok(())
}

impl eframe::App for MainApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
if self.tasks.is_empty() {
self.view = View::TaskList;
}

match &self.view {
View::ActiveTask => self.update_active_task(ctx, frame),
View::TaskList => self.update_task_list(ctx, frame),
}
fn update(app: &mut App, message: Message) {
match update_inner(app, message) {
Ok(()) => {},
Err(msg) => eprintln!("{}", msg),
}
}

// This function and `self.needs_save` are part of a workaround for
// https://github.com/emilk/egui/issues/5243
fn auto_save_interval(&self) -> std::time::Duration {
if self.needs_save {
std::time::Duration::from_millis(0)
} else {
std::time::Duration::from_secs(30)
}
}
fn view(_app: &App) -> Element<Message> {
text("hello").into()
}

fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, &self.tasks);
self.needs_save = false;
}
fn main() -> iced::Result {
iced::run("STAYFOCUSED", update, view)
}
Loading