mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-06-17 15:34:18 +08:00
Add mouse handling
This commit is contained in:
parent
2af2a5bd26
commit
51a5ea0848
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -444,7 +444,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -611,6 +611,17 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "881c5d0a13b2f1498e2306e82cbada78390e152d4b1378fb28a84f4dcd0dc4f3"
|
||||
dependencies = [
|
||||
"dispatch",
|
||||
"nix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@ -742,6 +753,12 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@ -1537,6 +1554,18 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if 1.0.1",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
@ -2007,6 +2036,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"blake3 1.5.3",
|
||||
"ctrlc",
|
||||
"ignore",
|
||||
"object",
|
||||
"pbr",
|
||||
@ -3164,7 +3194,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@ -3197,13 +3227,19 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3212,7 +3248,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3251,6 +3287,15 @@ dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
|
||||
@ -23,6 +23,7 @@ doctest = false
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
blake3 = "=1.5.3" # 1.5.4 is incompatible with blake3 0.3 dependency from pkgar
|
||||
ctrlc = { version = "3.5.0", features = ["termination"] }
|
||||
ignore = "0.4"
|
||||
object = { version = "0.36", features = ["build_core"] }
|
||||
pbr = "1.0.2"
|
||||
@ -36,9 +37,11 @@ serde = { version = "=1.0.197", features = ["derive"] }
|
||||
termion = "4"
|
||||
toml = "0.8"
|
||||
walkdir = "2.3.1"
|
||||
ratatui = { version = "0.29.0", default-features = false, features = [
|
||||
"termion",
|
||||
] }
|
||||
|
||||
[dependencies.ratatui]
|
||||
version = "0.29.0"
|
||||
default-features = false
|
||||
features = ["termion"]
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
@ -60,6 +60,9 @@ function pkgar {
|
||||
function cook {
|
||||
"$ROOT/target/release/cook" "$@"
|
||||
}
|
||||
function repo {
|
||||
"$ROOT/target/release/repo" "$@"
|
||||
}
|
||||
function repo_builder {
|
||||
"$ROOT/target/release/repo_builder" "$@"
|
||||
}
|
||||
|
||||
5
repo.sh
5
repo.sh
@ -18,15 +18,12 @@ do
|
||||
elif [ "$arg" == "--nonstop" ]
|
||||
then
|
||||
COOK_OPT+=" --nonstop"
|
||||
elif [ "$arg" == "--offline" ]
|
||||
then
|
||||
COOK_OPT+=" --offline"
|
||||
else
|
||||
recipes+=" $arg"
|
||||
fi
|
||||
done
|
||||
|
||||
cook $COOK_OPT $recipes
|
||||
repo cook $COOK_OPT $recipes
|
||||
|
||||
repo="$ROOT/repo/$TARGET"
|
||||
mkdir -p "$repo"
|
||||
|
||||
235
src/bin/repo.rs
235
src/bin/repo.rs
@ -2,7 +2,8 @@ use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, PipeReader, stdout};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::time::Duration;
|
||||
use std::{env, fs};
|
||||
use std::{process, thread};
|
||||
@ -18,11 +19,14 @@ use cookbook::recipe::CookRecipe;
|
||||
use pkg::PackageName;
|
||||
use pkg::package::PackageError;
|
||||
use ratatui::Terminal;
|
||||
use ratatui::layout::{Constraint, Direction, Layout};
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Position, Rect};
|
||||
use ratatui::prelude::TermionBackend;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph, Wrap};
|
||||
use termion::screen::{ToAlternateScreen, ToMainScreen};
|
||||
use termion::event::{Event, Key, MouseEvent};
|
||||
use termion::input::{MouseTerminal, TermRead};
|
||||
use termion::raw::RawTerminal;
|
||||
use termion::screen::{AlternateScreen, ToAlternateScreen, ToMainScreen};
|
||||
|
||||
// A repo manager, to replace repo.sh
|
||||
|
||||
@ -322,6 +326,10 @@ fn handle_push(recipe: &CookRecipe, config: &CliConfig) -> anyhow::Result<()> {
|
||||
))
|
||||
}
|
||||
|
||||
//
|
||||
// ------------- TUI SPECIFIC CODE -------------------
|
||||
//
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum RecipeStatus {
|
||||
Pending,
|
||||
@ -341,6 +349,8 @@ enum StatusUpdate {
|
||||
CookLog(PackageName, String),
|
||||
Cooked(PackageName),
|
||||
FailCook(PackageName, String),
|
||||
FetchThreadFinished,
|
||||
CookThreadFinished,
|
||||
}
|
||||
|
||||
struct TuiApp {
|
||||
@ -352,6 +362,15 @@ struct TuiApp {
|
||||
active_fetch: Option<PackageName>,
|
||||
active_cook: Option<PackageName>,
|
||||
logs: HashMap<PackageName, Vec<String>>,
|
||||
log_scroll: u16,
|
||||
auto_scroll: bool,
|
||||
fetch_scroll: u16,
|
||||
cook_scroll: u16,
|
||||
fetch_complete: bool,
|
||||
cook_complete: bool,
|
||||
fetch_panel_rect: Option<Rect>,
|
||||
cook_panel_rect: Option<Rect>,
|
||||
log_panel_rect: Option<Rect>,
|
||||
}
|
||||
|
||||
impl TuiApp {
|
||||
@ -369,6 +388,15 @@ impl TuiApp {
|
||||
active_fetch: None,
|
||||
active_cook: None,
|
||||
logs: HashMap::new(),
|
||||
log_scroll: 0,
|
||||
auto_scroll: true,
|
||||
fetch_scroll: 0,
|
||||
cook_scroll: 0,
|
||||
fetch_complete: false,
|
||||
cook_complete: false,
|
||||
fetch_panel_rect: None,
|
||||
cook_panel_rect: None,
|
||||
log_panel_rect: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,14 +405,16 @@ impl TuiApp {
|
||||
let (name, new_status) = match update {
|
||||
StatusUpdate::StartFetch(name) => {
|
||||
self.active_fetch = Some(name.clone());
|
||||
self.logs.insert(name.clone(), Vec::new()); // Clear old logs
|
||||
self.logs.insert(name.clone(), Vec::new());
|
||||
self.log_scroll = 0;
|
||||
self.auto_scroll = true;
|
||||
(name.clone(), RecipeStatus::Fetching)
|
||||
}
|
||||
StatusUpdate::Fetched(name) => (name, RecipeStatus::Fetched),
|
||||
StatusUpdate::FailFetch(name, err) => (name, RecipeStatus::Failed(err)),
|
||||
StatusUpdate::StartCook(name) => {
|
||||
self.active_cook = Some(name.clone()); // Set active cook
|
||||
self.logs.insert(name.clone(), Vec::new()); // Clear old logs
|
||||
self.active_cook = Some(name.clone());
|
||||
self.logs.insert(name.clone(), Vec::new());
|
||||
(name.clone(), RecipeStatus::Cooking)
|
||||
}
|
||||
StatusUpdate::CookLog(name, line) => {
|
||||
@ -396,8 +426,28 @@ impl TuiApp {
|
||||
return; // Should not happen
|
||||
}
|
||||
}
|
||||
StatusUpdate::Cooked(name) => (name, RecipeStatus::Done),
|
||||
StatusUpdate::FailCook(name, err) => (name, RecipeStatus::Failed(err)),
|
||||
StatusUpdate::Cooked(name) => {
|
||||
if self.active_cook.as_ref() == Some(&name) {
|
||||
self.active_cook = None;
|
||||
}
|
||||
self.auto_scroll = true;
|
||||
(name.clone(), RecipeStatus::Done)
|
||||
}
|
||||
StatusUpdate::FailCook(name, err) => {
|
||||
if self.active_cook.as_ref() == Some(&name) {
|
||||
self.active_cook = None;
|
||||
}
|
||||
self.auto_scroll = false;
|
||||
(name.clone(), RecipeStatus::Failed(err))
|
||||
}
|
||||
StatusUpdate::FetchThreadFinished => {
|
||||
self.fetch_complete = true;
|
||||
return;
|
||||
}
|
||||
StatusUpdate::CookThreadFinished => {
|
||||
self.cook_complete = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((_, status)) = self.recipes.iter_mut().find(|(r, _)| r.name == name) {
|
||||
@ -432,26 +482,6 @@ impl TuiApp {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_log_reader(
|
||||
mut pipe_reader: PipeReader,
|
||||
package_name: PackageName,
|
||||
status_tx: mpsc::Sender<StatusUpdate>,
|
||||
) {
|
||||
thread::spawn(move || {
|
||||
let reader = BufReader::new(&mut pipe_reader);
|
||||
for line in reader.lines() {
|
||||
let line_str = line.unwrap_or_else(|e| format!("[IO Error] {}", e));
|
||||
if status_tx
|
||||
.send(StatusUpdate::CookLog(package_name.clone(), line_str))
|
||||
.is_err()
|
||||
{
|
||||
// TUI thread hung up
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<()> {
|
||||
let (work_tx, work_rx) = mpsc::channel::<(CookRecipe, PathBuf)>();
|
||||
let (status_tx, status_rx) = mpsc::channel::<StatusUpdate>();
|
||||
@ -475,6 +505,22 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
cooker_status_tx
|
||||
.send(StatusUpdate::CookThreadFinished)
|
||||
.unwrap_or_default();
|
||||
});
|
||||
|
||||
// ----- Input Thread -----
|
||||
let (input_tx, input_rx) = mpsc::channel::<Event>();
|
||||
let _input_handle = thread::spawn(move || {
|
||||
let stdin = std::io::stdin();
|
||||
for evt in stdin.events() {
|
||||
if let Ok(evt) = evt {
|
||||
if input_tx.send(evt).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Fetcher Thread ----
|
||||
@ -502,30 +548,37 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
|
||||
.send(StatusUpdate::FailFetch(name, e.to_string()))
|
||||
.unwrap(),
|
||||
}
|
||||
status_tx
|
||||
.send(StatusUpdate::FetchThreadFinished)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
});
|
||||
|
||||
print!("{}", ToAlternateScreen);
|
||||
// enable_raw_mode()?;
|
||||
let mut terminal = Terminal::new(TermionBackend::new(stdout()))?;
|
||||
terminal.clear()?;
|
||||
|
||||
let mut app = TuiApp::new(recipes);
|
||||
// let total_recipes = app.recipes.len();
|
||||
let mut running = true;
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
|
||||
while running {
|
||||
ctrlc::set_handler(move || {
|
||||
r.store(false, Ordering::SeqCst);
|
||||
})
|
||||
.context("Error setting Ctrl-C handler")?;
|
||||
|
||||
while running.load(Ordering::Relaxed) {
|
||||
terminal.draw(|f| {
|
||||
let mut constraints = Vec::new();
|
||||
if !app.fetch_complete {
|
||||
constraints.push(Constraint::Length(30));
|
||||
}
|
||||
constraints.push(Constraint::Length(30));
|
||||
constraints.push(Constraint::Min(20)); // Log panel always exists
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(60),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.constraints(constraints)
|
||||
.split(f.area());
|
||||
|
||||
// Left Pane
|
||||
@ -586,19 +639,85 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
|
||||
vec!["No active cook job.".to_string()]
|
||||
};
|
||||
|
||||
let log_pane_height = chunks[2].height.saturating_sub(2);
|
||||
let total_log_lines = log_text.len() as u16;
|
||||
|
||||
if app.auto_scroll {
|
||||
if total_log_lines > log_pane_height {
|
||||
app.log_scroll = total_log_lines - log_pane_height;
|
||||
} else {
|
||||
app.log_scroll = 0;
|
||||
}
|
||||
} else {
|
||||
if total_log_lines > log_pane_height {
|
||||
if app.log_scroll > total_log_lines - log_pane_height {
|
||||
app.log_scroll = total_log_lines - log_pane_height;
|
||||
}
|
||||
} else {
|
||||
app.log_scroll = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let log_paragraph = Paragraph::new(log_text.join("\n"))
|
||||
.block(Block::default().title(log_title).borders(Borders::ALL))
|
||||
.wrap(Wrap { trim: false });
|
||||
.wrap(Wrap { trim: false })
|
||||
.scroll((app.log_scroll, 0));
|
||||
|
||||
f.render_widget(log_paragraph, chunks[2]);
|
||||
|
||||
// let footer = Paragraph::new(format!(
|
||||
// "Done: {}/{} | Failed: {}",
|
||||
// app.done.len(),
|
||||
// total_recipes,
|
||||
// app.failed.len()
|
||||
// ));
|
||||
// f.render_widget(footer, f.area());
|
||||
while let Ok(event) = input_rx.try_recv() {
|
||||
match event {
|
||||
Event::Key(key) => match key {
|
||||
Key::Up => {
|
||||
app.auto_scroll = false;
|
||||
app.log_scroll = app.log_scroll.saturating_sub(1);
|
||||
}
|
||||
Key::Down => {
|
||||
app.auto_scroll = false;
|
||||
app.log_scroll = app.log_scroll.saturating_add(1);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Event::Mouse(mouse_event) => {
|
||||
match mouse_event {
|
||||
MouseEvent::Press(termion::event::MouseButton::WheelUp, x, y) => {
|
||||
// termion is 1-based, ratatui rects are 0-based
|
||||
let pos = Position {
|
||||
x: x.saturating_sub(1),
|
||||
y: y.saturating_sub(1),
|
||||
};
|
||||
|
||||
if app.fetch_panel_rect.map_or(false, |r| r.contains(pos)) {
|
||||
app.fetch_scroll = app.fetch_scroll.saturating_sub(1);
|
||||
} else if app.cook_panel_rect.map_or(false, |r| r.contains(pos)) {
|
||||
app.cook_scroll = app.cook_scroll.saturating_sub(1);
|
||||
} else if app.log_panel_rect.map_or(false, |r| r.contains(pos)) {
|
||||
app.auto_scroll = false;
|
||||
app.log_scroll = app.log_scroll.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
MouseEvent::Press(termion::event::MouseButton::WheelDown, x, y) => {
|
||||
let pos = Position {
|
||||
x: x.saturating_sub(1),
|
||||
y: y.saturating_sub(1),
|
||||
};
|
||||
|
||||
if app.fetch_panel_rect.map_or(false, |r| r.contains(pos)) {
|
||||
app.fetch_scroll = app.fetch_scroll.saturating_add(1);
|
||||
} else if app.cook_panel_rect.map_or(false, |r| r.contains(pos)) {
|
||||
app.cook_scroll = app.cook_scroll.saturating_add(1);
|
||||
} else if app.log_panel_rect.map_or(false, |r| r.contains(pos)) {
|
||||
app.auto_scroll = false;
|
||||
app.log_scroll = app.log_scroll.saturating_add(1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
while let Ok(update) = status_rx.try_recv() {
|
||||
@ -607,7 +726,7 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
|
||||
|
||||
if fetcher_handle.is_finished() && cooker_handle.is_finished() {
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
running = false;
|
||||
running.swap(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,6 +739,26 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_log_reader(
|
||||
mut pipe_reader: PipeReader,
|
||||
package_name: PackageName,
|
||||
status_tx: mpsc::Sender<StatusUpdate>,
|
||||
) {
|
||||
thread::spawn(move || {
|
||||
let reader = BufReader::new(&mut pipe_reader);
|
||||
for line in reader.lines() {
|
||||
let line_str = line.unwrap_or_else(|e| format!("[IO Error] {}", e));
|
||||
if status_tx
|
||||
.send(StatusUpdate::CookLog(package_name.clone(), line_str))
|
||||
.is_err()
|
||||
{
|
||||
// TUI thread hung up
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn setup_logger(
|
||||
cooker_status_tx: &mpsc::Sender<StatusUpdate>,
|
||||
name: &PackageName,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user