feat: Add font, find system fonts (#4)

* feat: Add font, find system fonts

* fix: File time set error and missing token permission to set file time and acl

* fix: File permission get and set not working

* fix: Missing font library in linux env
This commit is contained in:
DaLaw2 2025-08-02 02:54:24 +08:00 committed by GitHub
parent f72c66d348
commit 9c16e05c8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 390 additions and 102 deletions

View File

@ -15,7 +15,7 @@ jobs:
- name: Install system dependencies
run: |
apt-get update
apt-get install -y curl build-essential mingw-w64 git
apt-get install -y curl build-essential mingw-w64 git libfontconfig1-dev pkg-config
- name: Checkout code
uses: actions/checkout@v4

View File

@ -15,7 +15,7 @@ jobs:
- name: Install system dependencies
run: |
apt-get update
apt-get install -y curl build-essential mingw-w64 git
apt-get install -y curl build-essential mingw-w64 git libfontconfig1-dev pkg-config
- name: Checkout code
uses: actions/checkout@v4

121
Cargo.lock generated
View File

@ -16,6 +16,7 @@ dependencies = [
"digest",
"eframe",
"egui-file-dialog",
"font-kit",
"fs4",
"futures",
"image",
@ -921,6 +922,18 @@ dependencies = [
"libc",
]
[[package]]
name = "core-text"
version = "20.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
dependencies = [
"core-foundation 0.9.4",
"core-graphics",
"foreign-types",
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -1074,6 +1087,15 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
@ -1155,6 +1177,18 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "dwrote"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be"
dependencies = [
"lazy_static",
"libc",
"winapi",
"wio",
]
[[package]]
name = "ecolor"
version = "0.32.0"
@ -1483,6 +1517,12 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "float-ord"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
[[package]]
name = "flume"
version = "0.11.1"
@ -1500,6 +1540,31 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "font-kit"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
dependencies = [
"bitflags 2.9.1",
"byteorder",
"core-foundation 0.9.4",
"core-graphics",
"core-text",
"dirs",
"dwrote",
"float-ord",
"freetype-sys",
"lazy_static",
"libc",
"log",
"pathfinder_geometry",
"pathfinder_simd",
"walkdir",
"winapi",
"yeslogic-fontconfig-sys",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -1536,6 +1601,17 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "freetype-sys"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "fs4"
version = "0.13.1"
@ -2970,6 +3046,31 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathfinder_geometry"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
dependencies = [
"log",
"pathfinder_simd",
]
[[package]]
name = "pathfinder_simd"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57"
dependencies = [
"rustc_version",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@ -5556,6 +5657,15 @@ dependencies = [
"toml 0.5.11",
]
[[package]]
name = "wio"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
dependencies = [
"winapi",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
@ -5634,6 +5744,17 @@ version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
[[package]]
name = "yeslogic-fontconfig-sys"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd"
dependencies = [
"dlib",
"once_cell",
"pkg-config",
]
[[package]]
name = "yoke"
version = "0.8.0"

View File

@ -2,8 +2,8 @@
name = "MirrorSphere"
version = "0.0.9"
edition = "2024"
authors = ["DaLaw2"]
build = "build.rs"
authors = ["DaLaw2"]
[dependencies]
async-trait = "0.1.88"
@ -17,6 +17,7 @@ digest = "0.10.7"
eframe = "0.32.0"
egui-file-dialog = "0.11.0"
fs4 = { version = "0.13.1", features = ["sync", "tokio"] }
font-kit = "0.14.3"
futures = "0.3.31"
image = "0.25.6"
libc = "0.2.169"

View File

@ -67,6 +67,17 @@ impl BackupEngine {
}
}
pub fn get_execution(&self, uuid: &Uuid) -> Option<BackupExecution> {
self.executions.get(uuid).map(|entry| entry.clone())
}
pub fn get_all_executions(&self) -> Vec<(Uuid, BackupExecution)> {
self.executions
.iter()
.map(|entry| (*entry.key(), entry.value().clone()))
.collect()
}
pub async fn add_execution(&self, execution: BackupExecution) {
self.executions.insert(execution.uuid, execution);
}

View File

@ -1,13 +1,14 @@
use crate::core::app_config::AppConfig;
use crate::core::backup_engine::BackupEngine;
use crate::core::event_bus::EventBus;
use crate::core::schedule_manager::ScheduleManager;
use crate::model::error::Error;
use crate::model::error::misc::MiscError;
use crate::ui::main_page::MainPage;
use crate::utils::assets::Assets;
use crate::utils::font;
use eframe::egui;
use std::sync::Arc;
use crate::core::app_config::AppConfig;
use crate::utils::assets::Assets;
pub struct GuiManager {
app_config: Arc<AppConfig>,
@ -50,7 +51,8 @@ impl GuiManager {
eframe::run_native(
"MirrorSphere",
options,
Box::new(|_| {
Box::new(|cc| {
font::setup_system_fonts(&cc.egui_ctx);
Ok(Box::new(MainPage::new(
config,
event_bus,

View File

@ -2,7 +2,7 @@ use crate::platform::raii_guard::SecurityDescriptorGuard;
use std::time::SystemTime;
use windows::Win32::Security::{ACL, PSID};
#[derive(Clone, Eq)]
#[derive(Debug, Clone, Eq)]
pub struct Attributes {
pub attributes: u32,
pub creation_time: SystemTime,
@ -16,6 +16,7 @@ impl PartialEq for Attributes {
}
}
#[derive(Debug)]
pub struct Permissions {
pub owner: PSID,
pub primary_group: PSID,

View File

@ -1,21 +1,22 @@
use crate::model::error::Error;
use crate::model::error::misc::MiscError;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::{env, mem};
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID};
use windows::Win32::Foundation::{CloseHandle, GetLastError, ERROR_NOT_ALL_ASSIGNED, HANDLE, LUID};
use windows::Win32::Security::{
AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED,
SE_SECURITY_NAME, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY,
AdjustTokenPrivileges, LUID_AND_ATTRIBUTES, LookupPrivilegeValueW, SE_BACKUP_NAME,
SE_PRIVILEGE_ENABLED, SE_RESTORE_NAME, SE_SECURITY_NAME, TOKEN_ADJUST_PRIVILEGES,
TOKEN_PRIVILEGES, TOKEN_QUERY,
};
use windows::Win32::System::Com::{
CoInitializeEx, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE,
COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, CoInitializeEx,
};
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
use windows::Win32::UI::Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW};
use windows::Win32::UI::Shell::{SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, ShellExecuteExW};
use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;
use windows::core::PCWSTR;
#[allow(dead_code)]
pub fn elevate() -> Result<(), Error> {
@ -34,9 +35,7 @@ pub fn elevate() -> Result<(), Error> {
.chain(Some(0))
.collect::<Vec<_>>();
unsafe {
win_runas(file, params)
}
unsafe { win_runas(file, params) }
}
#[allow(dead_code)]
@ -75,37 +74,42 @@ pub fn adjust_token_privileges() -> Result<(), Error> {
)
.map_err(SystemError::AdjustTokenPrivilegesFailed)?;
let mut luid = LUID {
LowPart: 0,
HighPart: 0,
};
let privileges = [SE_SECURITY_NAME, SE_BACKUP_NAME, SE_RESTORE_NAME];
LookupPrivilegeValueW(PCWSTR::null(), SE_SECURITY_NAME, &mut luid)
for privilege_name in privileges {
let mut luid = LUID {
LowPart: 0,
HighPart: 0,
};
LookupPrivilegeValueW(PCWSTR::null(), privilege_name, &mut luid)
.map_err(SystemError::AdjustTokenPrivilegesFailed)?;
let mut token_privilege = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [LUID_AND_ATTRIBUTES {
Luid: luid,
Attributes: SE_PRIVILEGE_ENABLED,
}],
};
AdjustTokenPrivileges(
token_handle,
false,
Some(&mut token_privilege),
0,
None,
None,
)
.map_err(SystemError::AdjustTokenPrivilegesFailed)?;
let mut token_privilege = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [LUID_AND_ATTRIBUTES {
Luid: luid,
Attributes: SE_PRIVILEGE_ENABLED,
}],
};
AdjustTokenPrivileges(
token_handle,
false,
Some(&mut token_privilege),
0,
None,
None,
)
.map_err(SystemError::AdjustTokenPrivilegesFailed)?;
}
CloseHandle(token_handle).map_err(MiscError::ObjectFreeFailed)?;
// if GetLastError().is_err() {
// Err(SystemError::AdjustTokenPrivilegesFailed)?
// }
let last_error = GetLastError();
if last_error == ERROR_NOT_ALL_ASSIGNED {
Err(SystemError::AdjustTokenPrivilegesFailed(format!("{:?}", last_error)))?
}
Ok(())
}

View File

@ -1,8 +1,8 @@
use crate::interface::file_system::FileSystemTrait;
use crate::model::error::Error;
use crate::model::error::io::IOError;
use crate::model::error::misc::MiscError;
use crate::model::error::system::SystemError;
use crate::model::error::Error;
use crate::platform::attributes::{Attributes, Permissions};
use crate::platform::raii_guard::SecurityDescriptorGuard;
use async_trait::async_trait;
@ -15,17 +15,20 @@ use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::Semaphore;
use tokio::task::spawn_blocking;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, FILETIME, GENERIC_ALL, SYSTEMTIME};
use windows::Win32::Foundation::{CloseHandle, FILETIME, GENERIC_WRITE, SYSTEMTIME};
use windows::Win32::Security::Authorization::{
GetNamedSecurityInfoW, SetNamedSecurityInfoW, SE_FILE_OBJECT,
GetNamedSecurityInfoW, SE_FILE_OBJECT, SetNamedSecurityInfoW,
};
use windows::Win32::Security::{
ACL, BACKUP_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION,
OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID, SACL_SECURITY_INFORMATION,
};
use windows::Win32::Security::{ACL, BACKUP_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID};
use windows::Win32::Storage::FileSystem::{
CreateFileW, SetFileAttributesW, SetFileTime, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_DELETE,
FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_WRITE,
OPEN_EXISTING, SetFileAttributesW, SetFileTime,
};
use windows::Win32::System::Time::SystemTimeToFileTime;
use windows::core::PCWSTR;
pub struct FileSystem {
semaphore: Arc<Semaphore>,
@ -52,10 +55,9 @@ impl FileSystemTrait for FileSystem {
.await
.map_err(IOError::SemaphoreClosed)?;
let link_target =
tokio::fs::read_link(source_link)
.await
.map_err(|err| IOError::ReadSymbolLinkFailed(source_link.clone(), err))?;
let link_target = tokio::fs::read_link(source_link)
.await
.map_err(|err| IOError::ReadSymbolLinkFailed(source_link.clone(), err))?;
if link_target.is_dir() {
tokio::fs::symlink_dir(&link_target, destination_link).await
@ -121,11 +123,11 @@ impl FileSystemTrait for FileSystem {
let handle = CreateFileW(
PCWSTR(file_path_wild.as_ptr()),
GENERIC_ALL.0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
GENERIC_WRITE.0,
FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(file_attributes),
FILE_FLAGS_AND_ATTRIBUTES(FILE_FLAG_BACKUP_SEMANTICS.0),
None,
)
.map_err(|err| IOError::SetMetadataFailed(path.clone(), err))?;
@ -158,7 +160,10 @@ impl FileSystemTrait for FileSystem {
let path = path.clone();
let permission = spawn_blocking(move || unsafe {
let security_info = BACKUP_SECURITY_INFORMATION;
let security_info = OWNER_SECURITY_INFORMATION
| GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION
| SACL_SECURITY_INFORMATION;
let mut owner = PSID::default();
let mut primary_group = PSID::default();
let mut dacl: *mut ACL = ptr::null_mut();
@ -249,8 +254,7 @@ impl FileSystem {
let mut file_time = FILETIME::default();
unsafe {
SystemTimeToFileTime(&sys_time, &mut file_time)
.map_err(SystemError::UnexpectError)?;
SystemTimeToFileTime(&sys_time, &mut file_time).map_err(SystemError::UnexpectError)?;
Ok(file_time)
}
}

View File

@ -2,6 +2,7 @@ use std::ops::Deref;
use windows::Win32::Foundation::{LocalFree, HLOCAL};
use windows::Win32::Security::PSECURITY_DESCRIPTOR;
#[derive(Debug)]
pub struct SecurityDescriptorGuard {
descriptor: PSECURITY_DESCRIPTOR,
}

View File

@ -13,6 +13,8 @@ use futures::executor::block_on;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::mpsc::Receiver;
use std::time::{Duration, Instant};
use tracing::error;
use uuid::Uuid;
#[derive(Debug, Clone)]
@ -65,6 +67,7 @@ pub struct ExecutionPage {
pub auto_scroll_errors: bool,
pub show_completed_tasks: bool,
viewing_errors_for_task: Option<Uuid>,
last_refresh: Option<Instant>,
}
impl ExecutionPage {
@ -97,6 +100,7 @@ impl ExecutionPage {
auto_scroll_errors: true,
show_completed_tasks: true,
viewing_errors_for_task: None,
last_refresh: None,
}
}
@ -124,13 +128,93 @@ impl ExecutionPage {
}
}
fn sync_all_execution_states(&mut self) {
let latest_executions = self.backup_engine.get_all_executions();
let latest_ids: std::collections::HashSet<Uuid> = latest_executions.iter().map(|(id, _)| *id).collect();
for (task_id, latest_execution) in latest_executions {
if let Some(mut display) = self.executions.get_mut(&task_id) {
display.execution = latest_execution;
}
}
self.executions.retain(|task_id, _| latest_ids.contains(task_id));
self.error_messages.retain(|task_id, _| latest_ids.contains(task_id));
if let Some(viewing_id) = self.viewing_errors_for_task {
if !latest_ids.contains(&viewing_id) {
self.viewing_errors_for_task = None;
}
}
}
fn sync_execution_state(&mut self, task_id: Uuid) {
if let Some(latest_execution) = self.backup_engine.get_execution(&task_id) {
if let Some(mut display) = self.executions.get_mut(&task_id) {
display.execution = latest_execution;
}
}
}
fn handle_start_execution(&mut self, task_id: Uuid) {
match block_on(self.backup_engine.start_execution(task_id)) {
Ok(_) => self.sync_execution_state(task_id),
Err(err) => {
self.sync_execution_state(task_id);
error!("{}", err);
}
}
}
fn handle_suspend_execution(&mut self, task_id: Uuid) {
match block_on(self.backup_engine.suspend_execution(task_id)) {
Ok(_) => self.sync_execution_state(task_id),
Err(err) => {
self.sync_execution_state(task_id);
error!("{}", err);
}
}
}
fn handle_resume_execution(&mut self, task_id: Uuid) {
match block_on(self.backup_engine.resume_execution(task_id)) {
Ok(_) => self.sync_execution_state(task_id),
Err(err) => {
self.sync_execution_state(task_id);
error!("{}", err);
}
}
}
fn handle_remove_execution(&mut self, task_id: Uuid) {
block_on(self.backup_engine.remove_execution(&task_id));
self.executions.remove(&task_id);
}
pub fn update(&mut self, ctx: &egui::Context) {
self.process_events();
let should_refresh = match self.last_refresh {
None => true,
Some(last) => {
last.elapsed() > Duration::from_secs(self.app_config.ui_refresh_time as u64)
}
};
if should_refresh {
self.sync_all_execution_states();
self.last_refresh = Some(Instant::now());
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Backup Executions");
ui.horizontal(|ui| {
if ui.button("🔄 Refresh").clicked() {
self.sync_all_execution_states();
self.last_refresh = Some(Instant::now());
}
if ui.button(" Add Execution").clicked() {
self.show_add_task_dialog = true;
}
@ -214,7 +298,7 @@ impl ExecutionPage {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.label(format!(
"🗂️ {}",
"📁 {}",
task_display.execution.source_path.display()
));
ui.label(format!(
@ -224,12 +308,12 @@ impl ExecutionPage {
ui.horizontal(|ui| {
let (color, symbol) = match task_display.execution.state {
BackupState::Running => (egui::Color32::GREEN, ""),
BackupState::Suspended => (egui::Color32::YELLOW, ""),
BackupState::Running => (egui::Color32::GREEN, ""),
BackupState::Suspended => (egui::Color32::YELLOW, ""),
BackupState::Completed => (egui::Color32::BLUE, ""),
BackupState::Failed => (egui::Color32::RED, ""),
BackupState::Canceled => (egui::Color32::GRAY, ""),
BackupState::Pending => (egui::Color32::GRAY, ""),
BackupState::Canceled => (egui::Color32::GRAY, ""),
BackupState::Pending => (egui::Color32::GRAY, ""),
};
ui.colored_label(
@ -271,46 +355,26 @@ impl ExecutionPage {
}
match task_display.execution.state {
BackupState::Pending | BackupState::Suspended => {
if ui.button("▶️ Start").clicked() {
if let Err(e) =
block_on(self.backup_engine.start_execution(task_id))
{
eprintln!("Failed to start execution: {e:?}");
}
BackupState::Pending => {
if ui.button("▶ Start").clicked() {
self.handle_start_execution(task_id);
}
}
BackupState::Suspended => {
if ui.button("▶ Resume").clicked() {
self.handle_resume_execution(task_id);
}
}
BackupState::Running => {
if ui.button("⏸️ Pause").clicked() {
if let Err(e) =
block_on(self.backup_engine.suspend_execution(task_id))
{
eprintln!("Failed to suspend execution: {e:?}");
}
if ui.button("⏸ Pause").clicked() {
self.handle_suspend_execution(task_id);
}
}
_ => {}
}
if task_display.execution.state == BackupState::Suspended
&& ui.button("▶️ Resume").clicked()
{
let rt = tokio::runtime::Handle::current();
if let Err(e) =
rt.block_on(self.backup_engine.resume_execution(task_id))
{
eprintln!("Failed to resume execution: {e:?}");
}
}
if ui.button("🗑️").clicked() {
let rt = tokio::runtime::Handle::current();
rt.block_on(self.backup_engine.remove_execution(&task_id));
self.executions.remove(&task_id);
self.error_messages.remove(&task_id);
if self.viewing_errors_for_task == Some(task_id) {
self.viewing_errors_for_task = None;
}
if ui.button("🗑").clicked() {
self.handle_remove_execution(task_id);
}
});
});
@ -380,11 +444,9 @@ impl ExecutionPage {
},
};
block_on(self.backup_engine.add_execution(execution.clone()));
let execution_display = ExecutionDisplay::from(execution.clone());
self.executions.insert(execution.uuid, execution_display);
let rt = tokio::runtime::Handle::current();
rt.block_on(self.backup_engine.add_execution(execution));
self.reset_form();
}
@ -442,7 +504,7 @@ impl ExecutionPage {
ui.with_layout(
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
if ui.button("🗑 Clear All Errors").clicked() {
if ui.button("🗑 Clear All Errors").clicked() {
self.error_messages.remove(&task_id);
self.viewing_errors_for_task = None;
}
@ -482,7 +544,7 @@ impl ExecutionPage {
});
} else {
ui.vertical_centered(|ui| {
ui.label(" Cannot find error information for this execution");
ui.label(" Cannot find error information for this execution");
});
}
});

80
src/utils/font.rs Normal file
View File

@ -0,0 +1,80 @@
use std::sync::Arc;
use eframe::egui;
use font_kit::source::SystemSource;
use font_kit::family_name::FamilyName;
use font_kit::properties::{Properties, Weight, Style, Stretch};
pub fn setup_system_fonts(ctx: &egui::Context) {
let mut fonts = egui::FontDefinitions::default();
let system_source = SystemSource::new();
if let Ok(font_handle) = system_source.select_best_match(
&[FamilyName::Title("Times New Roman".to_string())],
&Properties {
weight: Weight::NORMAL,
style: Style::Normal,
stretch: Stretch::NORMAL,
}
) {
if let Ok(font) = font_handle.load() {
if let Some(font_data) = font.copy_font_data() {
fonts.font_data.insert(
"times_new_roman".to_owned(),
Arc::from(egui::FontData::from_owned(font_data.to_vec()))
);
}
}
}
if let Ok(font_handle) = system_source.select_best_match(
&[
FamilyName::Title("DFKai-SB".to_string()),
FamilyName::Title("BiauKai".to_string()),
],
&Properties::default()
) {
if let Ok(font) = font_handle.load() {
if let Some(font_data) = font.copy_font_data() {
fonts.font_data.insert(
"kaiti".to_owned(),
Arc::from(egui::FontData::from_owned(font_data.to_vec()))
);
}
}
}
if let Ok(emoji_handle) = system_source.select_best_match(
&[
FamilyName::Title("Segoe UI Symbol".to_string()),
FamilyName::Title("Segoe UI Emoji".to_string()),
],
&Properties::default()
) {
if let Ok(font) = emoji_handle.load() {
if let Some(font_data) = font.copy_font_data() {
fonts.font_data.insert(
"system_emoji".to_owned(),
Arc::from(egui::FontData::from_owned(font_data.to_vec()))
);
}
}
}
fonts.families.get_mut(&egui::FontFamily::Proportional)
.unwrap()
.insert(0, "times_new_roman".to_owned());
fonts.families.get_mut(&egui::FontFamily::Proportional)
.unwrap()
.insert(1, "kaiti".to_owned());
fonts.families.get_mut(&egui::FontFamily::Proportional)
.unwrap()
.insert(2, "system_emoji".to_owned());
fonts.families.get_mut(&egui::FontFamily::Monospace)
.unwrap()
.insert(0, "times_new_roman".to_owned());
fonts.families.get_mut(&egui::FontFamily::Monospace)
.unwrap()
.insert(1, "kaiti".to_owned());
ctx.set_fonts(fonts);
}

View File

@ -2,4 +2,5 @@ pub mod assets;
pub mod database_lock;
pub mod file_hash;
pub mod file_lock;
pub mod font;
pub mod logging;