aaaaaaaaaa
This commit is contained in:
parent
f49df04b5a
commit
d3a259c764
File diff suppressed because it is too large
Load Diff
|
|
@ -14,21 +14,15 @@ rsn = "0.1.0"
|
|||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
known-folders = "1.2.0"
|
||||
indexmap = { version = "2.6.0", features = ["serde"] }
|
||||
notan = { version = "0.12.1", features = [
|
||||
"audio",
|
||||
"clipboard",
|
||||
"drop_files",
|
||||
"egui",
|
||||
"extra",
|
||||
"links",
|
||||
"notan_extra",
|
||||
"text",
|
||||
] }
|
||||
tao = "0.30.3"
|
||||
rocket = "0.5.1"
|
||||
async-nats = "0.37.0"
|
||||
flume = "0.11.0"
|
||||
bincode = "1.3.3"
|
||||
macroquad = "0.4.0"
|
||||
egui = "0.28"
|
||||
miniquad = "=0.4.0"
|
||||
egui-miniquad = "=0.15.0"
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1.17"
|
||||
|
|
|
|||
|
|
@ -5,193 +5,164 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::process::Child;
|
||||
use std::str::FromStr;
|
||||
use std::thread::spawn;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
|
||||
use egui::Sense;
|
||||
use egui::{self, Label};
|
||||
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};
|
||||
use macroquad::prelude::*;
|
||||
use miniquad::window::{dropped_file_count, dropped_file_path};
|
||||
use rocket::futures::StreamExt;
|
||||
use workspace::{egui_mq, LogFile, WorkspaceClientMessage, WorkspaceServerMessage};
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
struct LogFile {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(AppState, Serialize, Deserialize)]
|
||||
struct State {
|
||||
log_history: IndexSet<LogFile>,
|
||||
#[serde(skip)]
|
||||
log_viewer_path: PathBuf,
|
||||
#[serde(skip)]
|
||||
open_files: HashMap<PathBuf, Child>,
|
||||
}
|
||||
|
||||
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 window_conf() -> Conf {
|
||||
Conf {
|
||||
window_title: "Log History".to_owned(),
|
||||
high_dpi: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let win = WindowConfig::new()
|
||||
.set_vsync(false)
|
||||
.set_title("Log History")
|
||||
.set_resizable(true)
|
||||
.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 => {
|
||||
// for (_, mut child) in state.open_files.drain() {
|
||||
// if let Ok(Some(_)) = child.try_wait() {
|
||||
// continue;
|
||||
// }
|
||||
// _ = child.kill();
|
||||
// }
|
||||
}
|
||||
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")
|
||||
.exact_width(app.window().width() as f32)
|
||||
.show(ctx, |ui| {
|
||||
ui.heading("Log History");
|
||||
ui.separator();
|
||||
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() {
|
||||
if let Some(child) = state.open_files.get_mut(&log.path) {
|
||||
if let Ok(Some(_)) = child.try_wait() {
|
||||
*child = Command::new(&state.log_viewer_path)
|
||||
.arg(&path_text)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
state.open_files.insert(
|
||||
log.path.clone(),
|
||||
Command::new(&state.log_viewer_path)
|
||||
.arg(&path_text)
|
||||
.spawn()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
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 {
|
||||
#[macroquad::main(window_conf)]
|
||||
async fn main() {
|
||||
let log_viewer_path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
|
||||
.map(|exe| exe.join("fast_log_viewer.exe"))
|
||||
.unwrap_or_else(|| PathBuf::from("fast_log_viewer.exe"));
|
||||
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);
|
||||
let mut log_history: IndexSet<LogFile> = IndexSet::new();
|
||||
let mut open_files = HashMap::<PathBuf, Child>::new();
|
||||
|
||||
let (incoming_tx, incoming_rx) = flume::bounded(1024);
|
||||
let (outgoing_tx, outgoing_rx) = flume::bounded::<(&str, Vec<u8>)>(1024);
|
||||
|
||||
spawn(move || {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let nats = async_nats::connect("nats://localhost:42666").await.unwrap();
|
||||
nats.publish(
|
||||
"workspace.server",
|
||||
bincode::serialize(&WorkspaceClientMessage::GetLogHistory)
|
||||
.unwrap()
|
||||
.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut sub = nats.subscribe("workspace.log_history").await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
while let Some(msg) = sub.next().await {
|
||||
if let Ok(msg) =
|
||||
bincode::deserialize::<WorkspaceServerMessage>(&msg.payload)
|
||||
{
|
||||
match msg {
|
||||
WorkspaceServerMessage::LogHistory(history) => {
|
||||
incoming_tx.send_async(history).await.unwrap();
|
||||
}
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
while let Ok((topic, msg)) = outgoing_rx.recv_async().await {
|
||||
nats.publish(topic, msg.into()).await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
loop {
|
||||
clear_background(WHITE);
|
||||
|
||||
egui_mq::ui(|ctx| {
|
||||
egui::SidePanel::left("side_panel")
|
||||
.exact_width(500.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.heading("Log History");
|
||||
ui.separator();
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
// let mut to_swap = None;
|
||||
let mut to_delete = None;
|
||||
if let Ok(history) = incoming_rx.try_recv() {
|
||||
log_history = history;
|
||||
}
|
||||
let history_len = log_history.len();
|
||||
for (i, log) in 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() {
|
||||
if let Some(child) = open_files.get_mut(&log.path) {
|
||||
if let Ok(Some(_)) = child.try_wait() {
|
||||
*child = Command::new(&log_viewer_path)
|
||||
.arg(&path_text)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
open_files.insert(
|
||||
log.path.clone(),
|
||||
Command::new(&log_viewer_path)
|
||||
.arg(&path_text)
|
||||
.spawn()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
// to_swap = Some(i);
|
||||
}
|
||||
if i < history_len - 1 {
|
||||
ui.separator();
|
||||
}
|
||||
}
|
||||
// if let Some(i) = to_swap {
|
||||
// if let Some(value) = log_history.swap_remove_index(i) {
|
||||
// log_history.shift_insert(0, value);
|
||||
// }
|
||||
// }
|
||||
// if let Some(i) = to_delete {
|
||||
// _ = log_history.shift_remove_index(i);
|
||||
// }
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
let dropped_file_count = dropped_file_count();
|
||||
if dropped_file_count > 0 {
|
||||
for i in 0..dropped_file_count {
|
||||
if let Some(file) = dropped_file_path(i) {
|
||||
outgoing_tx
|
||||
.send((
|
||||
"workspace.server",
|
||||
bincode::serialize(&LogFile {
|
||||
name: file.file_name().unwrap().to_string_lossy().to_string(),
|
||||
path: file,
|
||||
})
|
||||
.unwrap()
|
||||
.into(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = state.unwrap_or_else(|| State {
|
||||
log_history: IndexSet::new(),
|
||||
log_viewer_path: PathBuf::new(),
|
||||
open_files: HashMap::new(),
|
||||
});
|
||||
state.log_viewer_path = log_viewer_path;
|
||||
state
|
||||
egui_mq::draw();
|
||||
|
||||
next_frame().await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
use egui_miniquad::EguiMq;
|
||||
use macroquad::prelude::*;
|
||||
use miniquad as mq;
|
||||
|
||||
pub use egui;
|
||||
pub use macroquad;
|
||||
|
||||
struct Egui {
|
||||
egui_mq: EguiMq,
|
||||
input_subscriber_id: usize,
|
||||
}
|
||||
|
||||
// Global variable and global functions because it's more like macroquad way
|
||||
static mut EGUI: Option<Egui> = None;
|
||||
|
||||
fn get_egui() -> &'static mut Egui {
|
||||
unsafe {
|
||||
if let Some(egui) = EGUI.as_mut() {
|
||||
egui
|
||||
} else {
|
||||
EGUI = Some(Egui::new());
|
||||
EGUI.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Egui {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
egui_mq: EguiMq::new(unsafe { get_internal_gl() }.quad_context),
|
||||
input_subscriber_id: macroquad::input::utils::register_input_subscriber(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ui<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut dyn mq::RenderingBackend, &egui::Context),
|
||||
{
|
||||
let gl = unsafe { get_internal_gl() };
|
||||
macroquad::input::utils::repeat_all_miniquad_input(self, self.input_subscriber_id);
|
||||
|
||||
self.egui_mq.run(gl.quad_context, f);
|
||||
}
|
||||
|
||||
fn draw(&mut self) {
|
||||
let mut gl = unsafe { get_internal_gl() };
|
||||
// Ensure that macroquad's shapes are not goint to be lost, and draw them now
|
||||
gl.flush();
|
||||
self.egui_mq.draw(gl.quad_context);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates egui ui. Must be called once per frame.
|
||||
pub fn ui<F: FnOnce(&egui::Context)>(f: F) {
|
||||
get_egui().ui(|_, ctx| f(ctx))
|
||||
}
|
||||
|
||||
/// Configure egui without beginning or ending a frame.
|
||||
pub fn cfg<F: FnOnce(&egui::Context)>(f: F) {
|
||||
f(get_egui().egui_mq.egui_ctx());
|
||||
}
|
||||
|
||||
/// Draw egui ui. Must be called after `ui` and once per frame.
|
||||
pub fn draw() {
|
||||
get_egui().draw()
|
||||
}
|
||||
|
||||
// Intended to be used only if you recreate the window, making the old EGUI instance invalid.
|
||||
#[doc(hidden)]
|
||||
pub fn reset_egui() {
|
||||
unsafe {
|
||||
EGUI = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl mq::EventHandler for Egui {
|
||||
fn update(&mut self) {}
|
||||
|
||||
fn draw(&mut self) {}
|
||||
|
||||
fn mouse_motion_event(&mut self, x: f32, y: f32) {
|
||||
self.egui_mq.mouse_motion_event(x, y);
|
||||
}
|
||||
|
||||
fn mouse_wheel_event(&mut self, dx: f32, dy: f32) {
|
||||
self.egui_mq.mouse_wheel_event(dx, dy);
|
||||
}
|
||||
|
||||
fn mouse_button_down_event(&mut self, mb: mq::MouseButton, x: f32, y: f32) {
|
||||
self.egui_mq.mouse_button_down_event(mb, x, y);
|
||||
}
|
||||
|
||||
fn mouse_button_up_event(&mut self, mb: mq::MouseButton, x: f32, y: f32) {
|
||||
self.egui_mq.mouse_button_up_event(mb, x, y);
|
||||
}
|
||||
|
||||
fn char_event(&mut self, character: char, _keymods: mq::KeyMods, _repeat: bool) {
|
||||
self.egui_mq.char_event(character);
|
||||
}
|
||||
|
||||
fn key_down_event(&mut self, keycode: mq::KeyCode, keymods: mq::KeyMods, _repeat: bool) {
|
||||
self.egui_mq.key_down_event(keycode, keymods);
|
||||
}
|
||||
|
||||
fn key_up_event(&mut self, keycode: mq::KeyCode, keymods: mq::KeyMods) {
|
||||
self.egui_mq.key_up_event(keycode, keymods);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod egui_mq;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum WorkspaceClientMessage {
|
||||
AddLogEntry(LogFile),
|
||||
GetLogHistory,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum WorkspaceServerMessage {
|
||||
LogHistory(IndexSet<LogFile>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct LogFile {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ use std::str::FromStr;
|
|||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use known_folders::{get_known_folder_path, KnownFolder};
|
||||
use rocket::futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tao::{
|
||||
|
|
@ -21,29 +22,24 @@ use tray_icon::{
|
|||
menu::{Menu, MenuEvent, MenuEventReceiver, MenuItem, PredefinedMenuItem},
|
||||
MouseButton, TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconEventReceiver,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
enum WorkspaceClientMessage {
|
||||
AddLogEntry(LogFile),
|
||||
GetLogHistory,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
enum WorkspaceServerMessage {
|
||||
LogHistory(IndexSet<LogFile>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Debug)]
|
||||
struct LogFile {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
use workspace::{LogFile, WorkspaceClientMessage};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
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() -> anyhow::Result<()> {
|
||||
let log_history_exe = std::env::current_exe()
|
||||
.ok()
|
||||
|
|
@ -92,7 +88,7 @@ fn main() -> anyhow::Result<()> {
|
|||
|
||||
rt.spawn(async move {
|
||||
let nats = async_nats::connect("nats://localhost:42666").await.unwrap();
|
||||
let mut sub = nats.subscribe("workspace.>").await.unwrap();
|
||||
let mut sub = nats.subscribe("workspace.server").await.unwrap();
|
||||
while let Some(msg) = sub.next().await {
|
||||
client_tx.send_async(msg).await.unwrap();
|
||||
}
|
||||
|
|
@ -174,6 +170,7 @@ fn main() -> anyhow::Result<()> {
|
|||
bincode::serialize(&state.log_history).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
state.save();
|
||||
}
|
||||
WorkspaceClientMessage::GetLogHistory => server_tx
|
||||
.send((
|
||||
|
|
|
|||
Loading…
Reference in New Issue