mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-14 04:43:42 +01:00
refactor(bar-core): move the memory, date and update widgets into komorebi-bar-core
Pure move of the three widget files that are byte-identical in the two bars since the previous revision. Each bar's widgets/mod.rs re-exports them (pub use komorebi_bar_core::widgets::memory / date / update). The core takes chrono, chrono-tz, reqwest (blocking) and serde_json (now a normal dependency, it was only a dev-dependency). Removed: 541 lines from each bar (138 + 245 + 158), 541 added once. Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; harness regeneration byte-identical for both (macOS 3,774 lines, Windows 9,091 lines). Not verified: rendering.
This commit is contained in:
1 parent
7cd85bd446
commit
6da27dda3c
12 files changed
+20
-550
No files matched your search
Generated
+3
@@ -3435,8 +3435,11 @@ dependencies = [
|
||||
name = "komorebi-bar-core"
|
||||
version = "0.1.42"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"eframe",
|
||||
"egui-phosphor",
|
||||
"reqwest",
|
||||
"schemars 1.2.3",
|
||||
"serde",
|
||||
"serde_json_lenient",
|
||||
|
||||
@@ -5,18 +5,19 @@ description = "The status bar shared by the Windows and macOS komorebi bars: con
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
chrono-tz = { workspace = true }
|
||||
eframe = { workspace = true }
|
||||
egui-phosphor = { git = "https://github.com/amPerl/egui-phosphor", rev = "d13688738478ecd12b426e3e74c59d6577a85b59" }
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
starship-battery = "0.11"
|
||||
sysinfo = { workspace = true }
|
||||
|
||||
# Optional dependencies
|
||||
schemars = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["schemars"]
|
||||
schemars = ["dep:schemars"]
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,3 +1,6 @@
|
||||
pub mod battery;
|
||||
pub mod cpu;
|
||||
pub mod date;
|
||||
pub mod memory;
|
||||
pub mod update;
|
||||
pub mod widget;
|
||||
File renamed without changes.
@@ -12,18 +12,18 @@ use std::sync::RwLock;
|
||||
pub mod applications;
|
||||
pub use komorebi_bar_core::widgets::battery;
|
||||
pub use komorebi_bar_core::widgets::cpu;
|
||||
pub mod date;
|
||||
pub use komorebi_bar_core::widgets::date;
|
||||
pub mod keyboard;
|
||||
pub mod komorebi;
|
||||
mod komorebi_layout;
|
||||
pub mod media;
|
||||
pub mod memory;
|
||||
pub use komorebi_bar_core::widgets::memory;
|
||||
pub mod network;
|
||||
pub mod storage;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod systray;
|
||||
pub mod time;
|
||||
pub mod update;
|
||||
pub use komorebi_bar_core::widgets::update;
|
||||
pub mod widget;
|
||||
|
||||
/// Global cache for icon images and their associated GPU textures.
|
||||
|
||||
Generated
+4
@@ -3243,10 +3243,14 @@ dependencies = [
|
||||
name = "komorebi-bar-core"
|
||||
version = "0.1.42"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"eframe",
|
||||
"egui-phosphor",
|
||||
"reqwest",
|
||||
"schemars 1.2.3",
|
||||
"serde",
|
||||
"serde_json_lenient",
|
||||
"starship-battery",
|
||||
"sysinfo 0.39.6",
|
||||
]
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::platform::platform;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use chrono::Local;
|
||||
use chrono_tz::Tz;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::WidgetText;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Custom format with additive modifiers for integer format specifiers
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CustomModifiers {
|
||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||
format: String,
|
||||
/// Additive modifiers for integer format specifiers (e.g. { "%U": 1 } to increment the zero-indexed week number by 1)
|
||||
modifiers: std::collections::HashMap<String, i32>,
|
||||
}
|
||||
|
||||
impl CustomModifiers {
|
||||
fn apply(&self, output: &str) -> String {
|
||||
let int_formatters = vec![
|
||||
"%Y", "%C", "%y", "%m", "%d", "%e", "%w", "%u", "%U", "%W", "%G", "%g", "%V", "%j",
|
||||
"%H", "%k", "%I", "%l", "%M", "%S", "%f",
|
||||
];
|
||||
|
||||
let mut modified_output = output.to_string();
|
||||
|
||||
for (modifier, value) in &self.modifiers {
|
||||
// check if formatter is integer type
|
||||
if !int_formatters.contains(&modifier.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the strftime value of modifier
|
||||
let formatted_modifier = Local::now().format(modifier).to_string();
|
||||
|
||||
// find the gotten value in the original output
|
||||
if let Some(pos) = modified_output.find(&formatted_modifier) {
|
||||
let start = pos;
|
||||
let end = start + formatted_modifier.len();
|
||||
// replace that value with the modified value
|
||||
if let Ok(num) = formatted_modifier.parse::<i32>() {
|
||||
modified_output.replace_range(start..end, &(num + value).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modified_output
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Date widget configuration
|
||||
pub struct DateConfig {
|
||||
/// Enable the Date widget
|
||||
pub enable: bool,
|
||||
/// Set the Date format
|
||||
pub format: DateFormat,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
///
|
||||
/// Use a custom format to display additional information, i.e.:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "Date": {
|
||||
/// "enable": true,
|
||||
/// "format": { "Custom": "%D %Z (Tokyo)" },
|
||||
/// "timezone": "Asia/Tokyo"
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
pub timezone: Option<String>,
|
||||
}
|
||||
|
||||
impl From<DateConfig> for Date {
|
||||
fn from(value: DateConfig) -> Self {
|
||||
let data_refresh_interval = 1;
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
format: value.format,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
||||
timezone: value.timezone,
|
||||
data_refresh_interval,
|
||||
last_state: String::new(),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Date widget format
|
||||
pub enum DateFormat {
|
||||
/// Month/Date/Year format (09/08/24)
|
||||
MonthDateYear,
|
||||
/// Year-Month-Date format (2024-09-08)
|
||||
YearMonthDate,
|
||||
/// Date-Month-Year format (8-Sep-2024)
|
||||
DateMonthYear,
|
||||
/// Day Date Month Year format (8 September 2024)
|
||||
DayDateMonthYear,
|
||||
/// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "Custom"))]
|
||||
Custom(String),
|
||||
/// Custom format with modifiers
|
||||
#[cfg_attr(feature = "schemars", schemars(title = "CustomModifiers"))]
|
||||
CustomModifiers(CustomModifiers),
|
||||
}
|
||||
|
||||
impl DateFormat {
|
||||
pub fn next(&mut self) {
|
||||
match self {
|
||||
DateFormat::MonthDateYear => *self = Self::YearMonthDate,
|
||||
DateFormat::YearMonthDate => *self = Self::DateMonthYear,
|
||||
DateFormat::DateMonthYear => *self = Self::DayDateMonthYear,
|
||||
DateFormat::DayDateMonthYear => *self = Self::MonthDateYear,
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fmt_string(&self) -> String {
|
||||
match self {
|
||||
DateFormat::MonthDateYear => String::from("%D"),
|
||||
DateFormat::YearMonthDate => String::from("%F"),
|
||||
DateFormat::DateMonthYear => String::from("%v"),
|
||||
DateFormat::DayDateMonthYear => String::from("%A %e %B %Y"),
|
||||
DateFormat::Custom(custom) => custom.to_string(),
|
||||
DateFormat::CustomModifiers(custom) => custom.format.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Date {
|
||||
pub enable: bool,
|
||||
pub format: DateFormat,
|
||||
label_prefix: LabelPrefix,
|
||||
timezone: Option<String>,
|
||||
data_refresh_interval: u64,
|
||||
last_state: String,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
fn output(&mut self) -> String {
|
||||
let mut output = self.last_state.clone();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
let formatted = match &self.timezone {
|
||||
Some(timezone) => match timezone.parse::<Tz>() {
|
||||
Ok(tz) => Local::now()
|
||||
.with_timezone(&tz)
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
Err(_) => format!("Invalid timezone: {timezone}"),
|
||||
},
|
||||
None => Local::now()
|
||||
.format(&self.format.fmt_string())
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
// if custom modifiers are used, apply them
|
||||
output = match &self.format {
|
||||
DateFormat::CustomModifiers(custom) => custom.apply(&formatted),
|
||||
_ => formatted,
|
||||
};
|
||||
|
||||
self.last_state.clone_from(&output);
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Date {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let mut output = self.output();
|
||||
if !output.is_empty() {
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
platform().calendar_icon().to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {
|
||||
output.insert_str(0, "DATE: ");
|
||||
}
|
||||
|
||||
layout_job.append(
|
||||
&output,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| {
|
||||
ui.add(
|
||||
Label::new(WidgetText::LayoutJob(Arc::from(layout_job.clone())))
|
||||
.selectable(false),
|
||||
)
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
self.format.next()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::platform::SystemTool;
|
||||
use crate::platform::platform;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use sysinfo::RefreshKind;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Memory widget configuration
|
||||
pub struct MemoryConfig {
|
||||
/// Enable the Memory widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval in seconds
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
/// Select when the current percentage is over this value [[1-100]]
|
||||
pub auto_select_over: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<MemoryConfig> for Memory {
|
||||
fn from(value: MemoryConfig) -> Self {
|
||||
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
system: System::new_with_specifics(
|
||||
RefreshKind::default().without_cpu().without_processes(),
|
||||
),
|
||||
data_refresh_interval,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||
auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct MemoryOutput {
|
||||
label: String,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
pub struct Memory {
|
||||
pub enable: bool,
|
||||
system: System,
|
||||
data_refresh_interval: u64,
|
||||
label_prefix: LabelPrefix,
|
||||
auto_select_over: Option<u8>,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn output(&mut self) -> MemoryOutput {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
||||
self.system.refresh_memory();
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
let used = self.system.used_memory();
|
||||
let total = self.system.total_memory();
|
||||
let usage = ((used * 100) / total) as u8;
|
||||
let selected = self.auto_select_over.is_some_and(|o| usage >= o);
|
||||
|
||||
MemoryOutput {
|
||||
label: match self.label_prefix {
|
||||
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
||||
format!("RAM: {usage}%")
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Icon => format!("{usage}%"),
|
||||
},
|
||||
selected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Memory {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let output = self.output();
|
||||
if !output.label.is_empty() {
|
||||
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
|
||||
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::MEMORY.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output.label,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let auto_focus_fill = config.auto_select_fill;
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
&& let Err(error) = platform().open_system_tool(SystemTool::TaskManager)
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,16 @@ use std::sync::RwLock;
|
||||
pub mod applications;
|
||||
pub use komorebi_bar_core::widgets::battery;
|
||||
pub use komorebi_bar_core::widgets::cpu;
|
||||
pub mod date;
|
||||
pub use komorebi_bar_core::widgets::date;
|
||||
// pub mod keyboard;
|
||||
pub mod komorebi;
|
||||
mod komorebi_layout;
|
||||
// pub mod media;
|
||||
pub mod memory;
|
||||
pub use komorebi_bar_core::widgets::memory;
|
||||
pub mod network;
|
||||
pub mod storage;
|
||||
pub mod time;
|
||||
pub mod update;
|
||||
pub use komorebi_bar_core::widgets::update;
|
||||
pub mod widget;
|
||||
|
||||
/// Global cache for icon images and their associated GPU textures.
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
use crate::config::LabelPrefix;
|
||||
use crate::platform::SystemTool;
|
||||
use crate::platform::platform;
|
||||
use crate::render::RenderConfig;
|
||||
use crate::selected_frame::SelectableFrame;
|
||||
use crate::widgets::widget::BarWidget;
|
||||
use eframe::egui::Align;
|
||||
use eframe::egui::Context;
|
||||
use eframe::egui::Label;
|
||||
use eframe::egui::TextFormat;
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::text::LayoutJob;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
/// Update widget configuration
|
||||
pub struct UpdateConfig {
|
||||
/// Enable the Update widget
|
||||
pub enable: bool,
|
||||
/// Data refresh interval in hours
|
||||
#[cfg_attr(feature = "schemars", schemars(extend("default" = 12)))]
|
||||
pub data_refresh_interval: Option<u64>,
|
||||
/// Display label prefix
|
||||
pub label_prefix: Option<LabelPrefix>,
|
||||
}
|
||||
|
||||
impl From<UpdateConfig> for Update {
|
||||
fn from(value: UpdateConfig) -> Self {
|
||||
let data_refresh_interval = value.data_refresh_interval.unwrap_or(12);
|
||||
|
||||
let mut latest_version = String::new();
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
if let Ok(response) = client
|
||||
.get(platform().latest_release_api_url())
|
||||
.header("User-Agent", "komorebi-bar-version-checker")
|
||||
.send()
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Release {
|
||||
tag_name: String,
|
||||
}
|
||||
|
||||
if let Ok(release) =
|
||||
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
|
||||
{
|
||||
let trimmed = release.tag_name.trim_start_matches("v");
|
||||
latest_version = trimmed.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
enable: value.enable,
|
||||
data_refresh_interval,
|
||||
installed_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
latest_version,
|
||||
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),
|
||||
last_updated: Instant::now()
|
||||
.checked_sub(Duration::from_secs(data_refresh_interval))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Update {
|
||||
pub enable: bool,
|
||||
data_refresh_interval: u64,
|
||||
installed_version: String,
|
||||
latest_version: String,
|
||||
label_prefix: LabelPrefix,
|
||||
last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Update {
|
||||
fn output(&mut self) -> String {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_updated)
|
||||
> Duration::from_secs((self.data_refresh_interval * 60) * 60)
|
||||
{
|
||||
let client = reqwest::blocking::Client::new();
|
||||
if let Ok(response) = client
|
||||
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
|
||||
.header("User-Agent", "komorebi-bar-version-checker")
|
||||
.send()
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Release {
|
||||
tag_name: String,
|
||||
}
|
||||
|
||||
if let Ok(release) =
|
||||
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
|
||||
{
|
||||
let trimmed = release.tag_name.trim_start_matches("v");
|
||||
self.latest_version = trimmed.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
self.last_updated = now;
|
||||
}
|
||||
|
||||
if self.latest_version > self.installed_version {
|
||||
format!("Update available! v{}", self.latest_version)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BarWidget for Update {
|
||||
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
||||
if self.enable {
|
||||
let output = self.output();
|
||||
if !output.is_empty() {
|
||||
let mut layout_job = LayoutJob::simple(
|
||||
match self.label_prefix {
|
||||
LabelPrefix::Icon | LabelPrefix::IconAndText => {
|
||||
egui_phosphor::regular::ROCKET_LAUNCH.to_string()
|
||||
}
|
||||
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
||||
},
|
||||
config.icon_font_id.clone(),
|
||||
ctx.style().visuals.selection.stroke.color,
|
||||
100.0,
|
||||
);
|
||||
|
||||
layout_job.append(
|
||||
&output,
|
||||
10.0,
|
||||
TextFormat {
|
||||
font_id: config.text_font_id.clone(),
|
||||
color: ctx.style().visuals.text_color(),
|
||||
valign: Align::Center,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
config.apply_on_widget(false, ui, |ui| {
|
||||
if SelectableFrame::new(false)
|
||||
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
||||
.clicked()
|
||||
&& let Err(error) = platform().open_system_tool(SystemTool::Url(format!(
|
||||
"https://github.com/LGUG2Z/komorebi/releases/v{}",
|
||||
self.latest_version
|
||||
)))
|
||||
{
|
||||
eprintln!("{error}")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user