aaaaaaaaaa

This commit is contained in:
Steve Biedermann 2024-10-09 17:17:52 +02:00
parent f49df04b5a
commit d3a259c764
6 changed files with 630 additions and 1934 deletions

2069
workspace/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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
}
}

108
workspace/src/egui_mq.rs Normal file
View File

@ -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);
}
}

23
workspace/src/lib.rs Normal file
View File

@ -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,
}

View File

@ -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((