Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ Cargo.lock
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/

# Test binaries
*.exe
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ repository = "https://github.com/Administroot/FreeEta"

[dependencies]
iced = {version = "0.13.1", features = ["image", "svg", "tokio", "advanced"] }
tokio = {version = "1.43.0", features = ["time", "full"]}
tokio = {version = "1.43.0", features = ["time", "full"]}
serde = "1.0"
serde_yml = "0.0.11"
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<div align="center">

# FreeEta
# ![LOGO](static/svg/logo.svg) FreeEta

[![Crates.io](https://img.shields.io/crates/v/FreeEta.svg)](https://crates.io/crates/FreeEta)
[![License](https://img.shields.io/crates/l/FreeEta.svg)](https://github.com/Administroot/FreeEta/blob/main/LICENSE)
![GitHub License](https://img.shields.io/github/license/Administroot/FreeEta)
[![Downloads](https://img.shields.io/crates/d/FreeEta.svg)](https://github.com/Administroot/FreeEta/releases/latest)
[![Test Status](https://img.shields.io/github/actions/workflow/status/administroot/FreeEta/test.yml?branch=master&event=push&label=test)](https://github.com/administroot/FreeEta/actions)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Administroot/FreeEta/rust.yml)
[![Made with iced](https://iced.rs/badge.svg)](https://github.com/iced-rs/iced)

A fast, elegant & free ETA analysis utility powered by 🦀 Rust, 🧊 iced.
Expand All @@ -14,18 +14,23 @@ A fast, elegant & free ETA analysis utility powered by 🦀 Rust, 🧊 iced.

</div>

> ❗❗❗<span style="color:rgb(255, 0, 0)">Main functions are not yet!</span>
> [!danger]+
> Freeeta is on flight🛫
>

## Installation

<a href="https://github.com/Administroot/FreeEta/releases">
<img src="static/svg/get_it_on_github.svg" alt="Get it on Github" width="200"/>
</a>

Support windows / linux / MacOS! Or use `cargo` to try it out:
Support windows / linux / MacOS!

Or use `cargo` to try it out:

```bash
cargo install --git https://github.com/administroot/FreeEta.git
```

## Why "FreeEta"?

Expand Down
3 changes: 3 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: FreeEta
lang: zh-cn
theme: Light
67 changes: 67 additions & 0 deletions src/freeeta_buttons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::freeeta_styles;
use crate::main_menu::MainMenuMessage;
use iced::widget::text::LineHeight;
use iced::widget::tooltip::Position;
use iced::widget::{button, Text, Tooltip};
use iced::{Alignment, Color, Font, Pixels, Theme};

#[allow(dead_code)]
/// Button '×'
pub fn button_hide<'a>(
message: MainMenuMessage,
font: Font,
) -> Tooltip<'a, MainMenuMessage, Theme> {
Tooltip::new(
button(
Text::new("×")
.font(font)
.align_y(Alignment::Center)
.align_x(Alignment::Center)
.size(15)
.line_height(LineHeight::Relative(1.0)),
)
.padding(2)
.height(20)
.width(20)
.on_press(message),
Text::new("Cancel").font(font),
Position::Right,
)
.gap(5)
}

/// Bookmark
pub fn bookmark<'a>(
message: MainMenuMessage,
font: Font,
inner_text: &str,
color: Color,
is_actived: bool,
) -> Tooltip<'a, MainMenuMessage, Theme> {
let mut button = button(
Text::new(inner_text.to_string())
.font(font)
.align_y(Alignment::Center)
.align_x(Alignment::Center)
.line_height(LineHeight::Relative(1.0)),
)
.style(move |theme, status| freeeta_styles::bookmark_style(theme, status, color, is_actived))
.height(40)
.on_press(message);

// Lengthen and hignlight the button when activated
button = match is_actived {
true => button.width(80),
false => button.width(60),
};

let bookmark = Tooltip::new(
button,
Text::new("Press to active / deactive")
.font(font)
.size(Pixels { 0: 15f32 }),
Position::Right,
)
.gap(5);
return bookmark;
}
28 changes: 28 additions & 0 deletions src/freeeta_picklists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::borrow::Borrow;

use iced::widget::pick_list;
use iced::{
widget::{text::Shaping, PickList},
Length,
};

/// Picklist to select functions.
pub fn functional_picklist<'a, T, L, V, Message, Theme, Renderer>(
options: L,
selected: Option<V>,
on_selected: impl Fn(T) -> Message + 'a,
placeholder: &str,
) -> PickList<'a, T, L, V, Message, Theme, Renderer>
where
T: ToString + PartialEq + Clone + 'a,
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
Theme: iced::widget::pick_list::Catalog + iced::widget::overlay::menu::Catalog,
Renderer: iced::advanced::text::Renderer,
{
pick_list::<'a, T, L, V, Message, Theme, Renderer>(options, selected, on_selected)
.width(Length::Shrink)
.placeholder(placeholder)
.text_shaping(Shaping::Advanced)
}
100 changes: 78 additions & 22 deletions src/freeeta_styles.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
use iced::{border::Radius, widget::pick_list, Background, Border, Color, Theme};

// pub fn radio_selected(_theme: &Theme, _status: radio::Status) -> radio::Style {
// radio::Style {
// text_color: Some(Color::from_rgb(0., 0., 1.)),
// background: Background::Color(Color::from_rgb(1., 1., 1.)),
// dot_color: Color::from_rgb(0., 0., 1.),
// border_width: 1.0,
// border_color: Color::from_rgb(0., 0., 1.),
// }
// }

// pub fn radio_unselected(_theme: &Theme, _status: radio::Status) -> radio::Style {
// radio::Style {
// text_color: Some(Color::from_rgb(0.5, 0.5, 0.5)),
// background: Background::Color(Color::from_rgb(1., 1., 1.)),
// dot_color: Color::from_rgb(0., 0., 1.),
// border_width: 1.0,
// border_color: Color::from_rgb(0., 0., 1.),
// }
// }
use iced::{
border::Radius,
widget::{button, pick_list, text},
Background, Border, Color, Theme,
};

// TODO: Read Theme from const in the future.
pub fn pick_list_unselected(_theme: &Theme, _status: pick_list::Status) -> pick_list::Style {
pub fn functional_picklist_style(_theme: &Theme, _status: pick_list::Status) -> pick_list::Style {
pick_list::Style {
text_color: Color::from_rgb(0.09, 0.02, 0.08),
placeholder_color: Color::from_rgb(0.09, 0.02, 0.08),
Expand All @@ -39,3 +23,75 @@ pub fn pick_list_unselected(_theme: &Theme, _status: pick_list::Status) -> pick_
},
}
}

pub fn bottomline_text_unselected(_theme: &Theme) -> text::Style {
text::Style {
color: Some(Color::from_rgb(0.35, 0.35, 0.34)),
}
}

/// Returns the average of two colors; color intensity is fixed to 100%
fn mix_colors(color_1: Color, color_2: Color) -> Color {
Color {
r: (color_1.r + color_2.r) / 2.0,
g: (color_1.g + color_2.g) / 2.0,
b: (color_1.b + color_2.b) / 2.0,
a: 1.0,
}
}

/// Half transparency of the given color.
fn half_transparency(color: Color) -> Color {
Color {
r: color.r,
g: color.g,
b: color.b,
a: color.a / 2.0,
}
}

pub fn bookmark_style(
_theme: &Theme,
status: button::Status,
color: Color,
is_actived: bool,
) -> button::Style {
let text_color = Color::BLACK;
let border = Border {
color,
width: 2.,
radius: Radius {
top_left: 0.,
top_right: 5.,
bottom_right: 5.,
bottom_left: 0.,
},
};

match (status, is_actived) {
(button::Status::Active, true) => button::Style {
background: Some(Background::Color(color)),
text_color,
border,
..Default::default()
},
(button::Status::Hovered, _) => button::Style {
background: Some(Background::Color(mix_colors(Color::WHITE, color))),
text_color,
border,
..Default::default()
},
(button::Status::Pressed, _) => button::Style {
background: Some(Background::Color(color)),
text_color,
border,
..Default::default()
},
(_, _) => button::Style {
background: Some(Background::Color(half_transparency(color))),
text_color,
border,
..Default::default()
},
}
}
34 changes: 34 additions & 0 deletions src/freeeta_yml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use serde::{Deserialize, Serialize};
use std::fs::{self, File};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct FreeEtaConfig {
/// Software name: typically "FreeEta"
name: String,
/// Language: ["zh-cn", "en-us"]
lang: String,
/// Theme: Default "Light"; Others see [iced::theme::Theme](https://docs.rs/iced/0.13.1/iced/theme/enum.Theme.html)
theme: String,
}

/// Read FreeEta config from ./
pub fn read_freeeta_config() -> Result<FreeEtaConfig, serde_yml::Error> {
let path = "config.yml";
if !fs::exists(path).unwrap() {
// If config doesn't exist, create a default config.
File::create(path).unwrap();
let default_config = FreeEtaConfig {
name: "FreeEta".to_string(),
lang: "zh-cn".to_string(),
theme: "Light".to_string(),
};
let yaml = serde_yml::to_string(&default_config)?;
fs::write(path, yaml).expect("Cannot write config.yml");
return Ok(default_config);
} else {
// If config exists, read from it.
let contents = fs::read_to_string(path).expect("Cannot read config.yml");
let deserialized_config: FreeEtaConfig = serde_yml::from_str(&contents)?;
return Ok(deserialized_config);
}
}
9 changes: 9 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
mod freeeta_buttons;
mod freeeta_picklists;
mod freeeta_styles;
mod freeeta_yml;
mod main_menu;
mod pages;

use iced;

fn main() -> iced::Result {
let freeeta_yml = freeeta_yml::read_freeeta_config().expect("[ERROR] CONFIG ERROR");
// TODO: Deal with the config struct.
println!("{:?}", freeeta_yml);
iced::application(
"FreeEta",
main_menu::FreeEta::update,
main_menu::FreeEta::view,
)
.theme(main_menu::FreeEta::theme)
.subscription(main_menu::FreeEta::subscription)
.run()
}
Loading
Loading