update
This commit is contained in:
parent
94bcff177e
commit
56f2901b8e
|
|
@ -4,6 +4,24 @@
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug 'log_history'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=workspace_log_history",
|
||||||
|
"--package=workspace"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "workspace_log_history",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
|
|
@ -6,13 +6,21 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tray-icon = "0.19.0"
|
tray-icon = "0.19.0"
|
||||||
image = "0.25"
|
image = "0.25"
|
||||||
notan = { version = "0.12.1", features = ["audio", "clipboard", "drop_files", "egui", "extra", "links", "notan_extra", "save_file", "serde", "text"] }
|
|
||||||
anyhow = "1.0.89"
|
anyhow = "1.0.89"
|
||||||
notan_extra = "0.12.1"
|
|
||||||
tokio = { version = "1.40.0", features = ["full"] }
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
indexmap = { version = "2.6.0", features = ["serde"] }
|
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
|
||||||
rsn = "0.1.0"
|
rsn = "0.1.0"
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
known-folders = "1.2.0"
|
known-folders = "1.2.0"
|
||||||
eframe = "0.29.1"
|
indexmap = { version = "2.6.0", features = ["serde"] }
|
||||||
egui = "0.29.1"
|
notan = { version = "0.12.1", features = [
|
||||||
|
"audio",
|
||||||
|
"clipboard",
|
||||||
|
"drop_files",
|
||||||
|
"egui",
|
||||||
|
"extra",
|
||||||
|
"links",
|
||||||
|
"notan_extra",
|
||||||
|
"text",
|
||||||
|
] }
|
||||||
|
tao = "0.30.3"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::{path::PathBuf, process::Command};
|
||||||
|
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use known_folders::{get_known_folder_path, KnownFolder};
|
||||||
|
use notan::egui::Sense;
|
||||||
|
use notan::log::warn;
|
||||||
|
use notan::{
|
||||||
|
draw::DrawConfig,
|
||||||
|
egui::{self, EguiConfig, EguiPluginSugar, Label},
|
||||||
|
extra::FpsLimit,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||||
|
struct LogFile {
|
||||||
|
name: String,
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AppState, Serialize, Deserialize)]
|
||||||
|
struct State {
|
||||||
|
log_history: IndexSet<LogFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn save(&mut self) {
|
||||||
|
let folder = get_known_folder_path(KnownFolder::LocalAppData)
|
||||||
|
.unwrap()
|
||||||
|
.join("workspace")
|
||||||
|
.join("log_history");
|
||||||
|
std::fs::create_dir_all(&folder).unwrap();
|
||||||
|
std::fs::write(folder.join("data.rsn"), rsn::to_string(self)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), String> {
|
||||||
|
let win = WindowConfig::new()
|
||||||
|
.set_vsync(false)
|
||||||
|
.set_lazy_loop(true)
|
||||||
|
.set_transparent(true)
|
||||||
|
.set_high_dpi(true);
|
||||||
|
|
||||||
|
notan::init_with(init)
|
||||||
|
.add_config(win)
|
||||||
|
.add_config(EguiConfig)
|
||||||
|
.add_config(DrawConfig)
|
||||||
|
.add_plugin(FpsLimit::new(60))
|
||||||
|
.update(update)
|
||||||
|
.event(event)
|
||||||
|
.draw(draw)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(_app: &mut App, _state: &mut State) {}
|
||||||
|
|
||||||
|
fn event(_app: &mut App, _assets: &mut Assets, state: &mut State, evt: Event) {
|
||||||
|
match evt {
|
||||||
|
Event::Exit => {}
|
||||||
|
Event::Drop(file) => {
|
||||||
|
if file.name.ends_with(".log") {
|
||||||
|
state.log_history.shift_insert(
|
||||||
|
0,
|
||||||
|
LogFile {
|
||||||
|
name: file.name.clone(),
|
||||||
|
path: file
|
||||||
|
.path
|
||||||
|
.unwrap_or_else(|| PathBuf::from_str(&file.name).unwrap()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
state.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(_app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut State) {
|
||||||
|
let mut output = plugins.egui(|ctx| {
|
||||||
|
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
||||||
|
ui.collapsing("Logs", |ui| {
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
let mut to_swap = None;
|
||||||
|
let mut to_delete = None;
|
||||||
|
let history_len = state.log_history.len();
|
||||||
|
for (i, log) in state.log_history.iter().enumerate() {
|
||||||
|
let path_text = log.path.to_string_lossy().to_string();
|
||||||
|
let label = ui
|
||||||
|
.add(Label::new(&log.name).sense(Sense::click()))
|
||||||
|
.on_hover_text(&path_text);
|
||||||
|
label.context_menu(|ui| {
|
||||||
|
if ui.button("Open in Notepad").clicked() {
|
||||||
|
_ = Command::new("notepad.exe").arg(&path_text).spawn();
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
if ui.button("Remove").clicked() {
|
||||||
|
to_delete = Some(i);
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if label.clicked() {
|
||||||
|
_ = Command::new("fast_log_viewer.exe").arg(path_text).spawn();
|
||||||
|
to_swap = Some(i);
|
||||||
|
}
|
||||||
|
if i < history_len - 1 {
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(i) = to_swap {
|
||||||
|
if let Some(value) = state.log_history.swap_remove_index(i) {
|
||||||
|
state.log_history.shift_insert(0, value);
|
||||||
|
}
|
||||||
|
state.save();
|
||||||
|
}
|
||||||
|
if let Some(i) = to_delete {
|
||||||
|
_ = state.log_history.shift_remove_index(i);
|
||||||
|
state.save();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
output.clear_color(Color::BLACK);
|
||||||
|
gfx.render(&output);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(_gfx: &mut Graphics) -> State {
|
||||||
|
let data = known_folders::get_known_folder_path(known_folders::KnownFolder::LocalAppData)
|
||||||
|
.unwrap()
|
||||||
|
.join("workspace")
|
||||||
|
.join("log_history")
|
||||||
|
.join("data.rsn");
|
||||||
|
|
||||||
|
let mut state = None;
|
||||||
|
if data.exists() {
|
||||||
|
match std::fs::read_to_string(data) {
|
||||||
|
Ok(data) => {
|
||||||
|
state = Some(rsn::from_str(&data).unwrap());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Couldn't read data file: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.unwrap_or_else(|| State {
|
||||||
|
log_history: IndexSet::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
232
src/main.rs
232
src/main.rs
|
|
@ -1,52 +1,52 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::{path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
use tao::{
|
||||||
use notan::{
|
event::{Event, WindowEvent},
|
||||||
draw::DrawConfig,
|
event_loop::{ControlFlow, EventLoop},
|
||||||
egui::{self, *},
|
window::WindowBuilder,
|
||||||
extra::FpsLimit,
|
|
||||||
log::warn,
|
|
||||||
prelude::*,
|
|
||||||
Event,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use tokio::process::{Child, Command};
|
||||||
use tokio::process::Command;
|
|
||||||
use tray_icon::{
|
use tray_icon::{
|
||||||
menu::{Menu, MenuEvent, MenuEventReceiver, MenuItem, PredefinedMenuItem},
|
menu::{Menu, MenuEvent, MenuEventReceiver, MenuItem, PredefinedMenuItem},
|
||||||
MouseButton, TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconEventReceiver,
|
MouseButton, TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconEventReceiver,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq)]
|
fn main() {
|
||||||
struct LogFile {
|
let event_loop = EventLoop::new();
|
||||||
name: String,
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TrayData {
|
let mut window = Some(
|
||||||
tray_icon: Option<TrayIcon>,
|
WindowBuilder::new()
|
||||||
quit_i: MenuItem,
|
.with_title("A fantastic window!")
|
||||||
menu_channel: MenuEventReceiver,
|
.with_visible(false)
|
||||||
tray_channel: TrayIconEventReceiver,
|
.build(&event_loop)
|
||||||
}
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
impl Default for TrayData {
|
let log_history_i = MenuItem::new("Logs", true, None);
|
||||||
fn default() -> Self {
|
let quit_i = MenuItem::new("Quit", true, None);
|
||||||
|
|
||||||
|
let mut tray_icon = None;
|
||||||
|
let mut log_history: Option<Child> = None;
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::WaitUntil(
|
||||||
|
std::time::Instant::now() + std::time::Duration::from_millis(16),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
|
||||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png");
|
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png");
|
||||||
|
|
||||||
let tray_menu = Menu::new();
|
let tray_menu = Menu::new();
|
||||||
|
|
||||||
let test = MenuItem::new("Test", true, None);
|
|
||||||
let quit_i = MenuItem::new("Quit", true, None);
|
|
||||||
tray_menu
|
tray_menu
|
||||||
.append_items(&[&test, &PredefinedMenuItem::separator(), &quit_i])
|
.append_items(&[&log_history_i, &PredefinedMenuItem::separator(), &quit_i])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let icon = load_icon(std::path::Path::new(path));
|
let icon = load_icon(std::path::Path::new(path));
|
||||||
|
|
||||||
let tray_icon = Some(
|
tray_icon = Some(
|
||||||
TrayIconBuilder::new()
|
TrayIconBuilder::new()
|
||||||
.with_menu_on_left_click(false)
|
.with_menu_on_left_click(false)
|
||||||
.with_menu(Box::new(tray_menu.clone()))
|
.with_menu(Box::new(tray_menu.clone()))
|
||||||
|
|
@ -56,157 +56,59 @@ impl Default for TrayData {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let menu_channel = MenuEvent::receiver().clone();
|
// We have to request a redraw here to have the icon actually show up.
|
||||||
let tray_channel = TrayIconEvent::receiver().clone();
|
// Tao only exposes a redraw method on the Window so we use core-foundation directly.
|
||||||
TrayData {
|
#[cfg(target_os = "macos")]
|
||||||
menu_channel,
|
unsafe {
|
||||||
tray_icon,
|
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
|
||||||
quit_i,
|
|
||||||
tray_channel,
|
let rl = CFRunLoopGetMain();
|
||||||
}
|
CFRunLoopWakeUp(rl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(AppState, Serialize, Deserialize)]
|
match event {
|
||||||
struct State {
|
Event::WindowEvent {
|
||||||
#[serde(skip)]
|
event: WindowEvent::CloseRequested,
|
||||||
tray_data: TrayData,
|
window_id: _,
|
||||||
log_history: IndexSet<LogFile>,
|
..
|
||||||
|
} => {
|
||||||
|
// drop the window to fire the `Destroyed` event
|
||||||
|
window = None;
|
||||||
}
|
}
|
||||||
|
Event::WindowEvent {
|
||||||
impl State {
|
event: WindowEvent::Destroyed,
|
||||||
fn save(&mut self) {
|
window_id: _,
|
||||||
let folder = known_folders::get_known_folder_path(known_folders::KnownFolder::LocalAppData)
|
..
|
||||||
.unwrap()
|
} => {
|
||||||
.join("workspace");
|
*control_flow = ControlFlow::Exit;
|
||||||
std::fs::create_dir_all(&folder).unwrap();
|
_ = tray_icon.take();
|
||||||
std::fs::write(folder.join("data.rsn"), rsn::to_string(self)).unwrap();
|
}
|
||||||
|
Event::MainEventsCleared => {
|
||||||
|
if let Some(w) = &window {
|
||||||
|
w.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), String> {
|
|
||||||
let win = WindowConfig::new()
|
|
||||||
.set_vsync(false)
|
|
||||||
.set_lazy_loop(true)
|
|
||||||
.set_transparent(true)
|
|
||||||
.set_high_dpi(true);
|
|
||||||
|
|
||||||
notan::init_with(init)
|
|
||||||
.add_config(win)
|
|
||||||
.add_config(EguiConfig)
|
|
||||||
.add_config(DrawConfig)
|
|
||||||
.add_plugin(FpsLimit::new(60))
|
|
||||||
.update(update)
|
|
||||||
.event(event)
|
|
||||||
.draw(draw)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(app: &mut App, state: &mut State) {
|
|
||||||
if let Ok(event) = MenuEvent::receiver().try_recv() {
|
if let Ok(event) = MenuEvent::receiver().try_recv() {
|
||||||
if event.id == state.tray_data.quit_i.id() {
|
if event.id == quit_i.id() {
|
||||||
state.tray_data.tray_icon.take();
|
window = None;
|
||||||
app.exit();
|
}
|
||||||
|
if event.id == log_history_i.id() {
|
||||||
|
match &mut log_history {
|
||||||
|
Some(log_history) => {
|
||||||
|
if let Ok(Some(_)) = log_history.try_wait() {
|
||||||
|
*log_history = Command::new("workspace_log_history").spawn().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
log_history = Some(Command::new("workspace_log_history").spawn().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(_app: &mut App, _assets: &mut Assets, state: &mut State, evt: Event) {
|
|
||||||
match evt {
|
|
||||||
Event::Exit => {}
|
|
||||||
Event::Drop(file) => {
|
|
||||||
if file.name.ends_with(".log") {
|
|
||||||
state.log_history.shift_insert(
|
|
||||||
0,
|
|
||||||
LogFile {
|
|
||||||
name: file.name.clone(),
|
|
||||||
path: file
|
|
||||||
.path
|
|
||||||
.unwrap_or_else(|| PathBuf::from_str(&file.name).unwrap()),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
state.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut State) {
|
|
||||||
let mut output = plugins.egui(|ctx| {
|
|
||||||
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
|
||||||
ui.collapsing("Logs", |ui| {
|
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
let mut to_swap = None;
|
|
||||||
let mut to_delete = None;
|
|
||||||
let history_len = state.log_history.len();
|
|
||||||
for (i, log) in state.log_history.iter().enumerate() {
|
|
||||||
let path_text = log.path.to_string_lossy().to_string();
|
|
||||||
let label = ui
|
|
||||||
.add(Label::new(&log.name).sense(Sense::click()))
|
|
||||||
.on_hover_text(&path_text);
|
|
||||||
label.context_menu(|ui| {
|
|
||||||
if ui.button("Open in Notepad").clicked() {
|
|
||||||
_ = Command::new("notepad.exe").arg(&path_text).spawn();
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
if ui.button("Remove").clicked() {
|
|
||||||
to_delete = Some(i);
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if label.clicked() {
|
|
||||||
_ = Command::new("C:\\tools\\fast-log-viewer\\fast_log_viewer.exe")
|
|
||||||
.arg(path_text)
|
|
||||||
.spawn();
|
|
||||||
to_swap = Some(i);
|
|
||||||
}
|
|
||||||
if i < history_len - 1 {
|
|
||||||
ui.separator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(i) = to_swap {
|
|
||||||
if let Some(value) = state.log_history.swap_remove_index(i) {
|
|
||||||
state.log_history.shift_insert(0, value);
|
|
||||||
}
|
|
||||||
state.save();
|
|
||||||
}
|
|
||||||
if let Some(i) = to_delete {
|
|
||||||
_ = state.log_history.shift_remove_index(i);
|
|
||||||
state.save();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
output.clear_color(Color::BLACK);
|
|
||||||
gfx.render(&output);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(_gfx: &mut Graphics) -> State {
|
|
||||||
let data = known_folders::get_known_folder_path(known_folders::KnownFolder::LocalAppData)
|
|
||||||
.unwrap()
|
|
||||||
.join("workspace")
|
|
||||||
.join("data.rsn");
|
|
||||||
|
|
||||||
let mut state = None;
|
|
||||||
if data.exists() {
|
|
||||||
match std::fs::read_to_string(data) {
|
|
||||||
Ok(data) => {
|
|
||||||
state = Some(rsn::from_str(&data).unwrap());
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!("Couldn't read data file: {}", err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
state.unwrap_or_else(|| State {
|
|
||||||
tray_data: TrayData::default(),
|
|
||||||
log_history: IndexSet::new(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_icon(path: &std::path::Path) -> tray_icon::Icon {
|
fn load_icon(path: &std::path::Path) -> tray_icon::Icon {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue