feat: a working run menu

This commit is contained in:
2024-05-21 20:51:34 +02:00
parent 8c6ae8f237
commit 926ea5ec3a
6 changed files with 314 additions and 4 deletions

54
Cargo.lock generated
View File

@@ -51,6 +51,18 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.4" version = "4.5.4"
@@ -109,6 +121,18 @@ version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.83" version = "1.0.83"
@@ -132,6 +156,30 @@ name = "runner"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"sdl2",
]
[[package]]
name = "sdl2"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8356b2697d1ead5a34f40bcc3c5d3620205fe0c7be0a14656223bfeec0258891"
dependencies = [
"bitflags",
"lazy_static",
"libc",
"sdl2-sys",
]
[[package]]
name = "sdl2-sys"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26bcacfdd45d539fb5785049feb0038a63931aa896c7763a2a12e125ec58bd29"
dependencies = [
"cfg-if",
"libc",
"version-compare",
] ]
[[package]] [[package]]
@@ -163,6 +211,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"

View File

@@ -8,3 +8,4 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
sdl2 = { version = "0.36.0", features = ["ttf"] }

12
src/config.rs Normal file
View File

@@ -0,0 +1,12 @@
pub const MAX_ITEM_DISPLAY_COUNT: u16 = 10;
pub const PADDING: u16 = 8;
pub const LINE_SPACING: u16 = 2;
pub const FONT_POINT_SIZE: u16 = 16;
pub const FONT_NAME: &str = "GeistMono Nerd Font";
pub const FONT_COLOR: &str = "#cdd6f4";
pub const FONT_COLOR_SELECTED: &str = "#1e1e2e";
pub const BACKGROUND_COLOR: &str = "#1e1e2e";
pub const BACKGROUND_COLOR_SELECTED: &str = "#89b4fa";

View File

@@ -1,19 +1,37 @@
use std::{error::Error, process::{self, Stdio}}; use std::{
error::Error,
process::{self, Stdio},
};
#[allow(unused_imports)]
use clap::Parser; use clap::Parser;
use executables::get_executables; use executables::get_executables;
use runner::Runner;
mod arguments; mod arguments;
mod executables; mod executables;
mod runner;
mod config;
mod utils;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let args = arguments::Arguments::parse(); // let args = arguments::Arguments::parse();
let executables = get_executables()?; let executables = get_executables()?;
let mut runner = Runner::new(executables);
if let Some(program) = runner.run() {
run_program(program);
}
Ok(()) Ok(())
} }
fn run(program: impl ToString) { fn run_program(program: impl ToString) {
let _ = process::Command::new(program.to_string()).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).spawn(); let _ = process::Command::new(program.to_string())
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
} }

209
src/runner/mod.rs Normal file
View File

@@ -0,0 +1,209 @@
use std::time::Duration;
use sdl2::{
event::Event,
keyboard::Keycode,
rect::Rect,
render::Canvas,
ttf::{self},
video::Window,
Sdl,
};
use crate::{
config::{
BACKGROUND_COLOR, FONT_COLOR, FONT_POINT_SIZE, LINE_SPACING, MAX_ITEM_DISPLAY_COUNT,
PADDING,
},
utils::color_from_hex,
};
pub struct Runner {
executables: Vec<String>,
context: Sdl,
canvas: Canvas<Window>,
ttf: ttf::Sdl2TtfContext,
input: String,
}
impl Runner {
pub fn new(executables: Vec<String>) -> Self {
let context = sdl2::init().expect("Error creating SDL context");
let ttf = ttf::init().expect("Error creating SDL TTF context");
let window_height: u32;
{
let font_path = String::from("/usr/share/fonts/OTF/GeistMonoNerdFontMono-Regular.otf");
let font = ttf
.load_font(font_path.clone(), FONT_POINT_SIZE)
.expect(&format!("Error loading font {}", font_path));
window_height = (PADDING
+ ((font.height() as u16 + LINE_SPACING) * (1 + MAX_ITEM_DISPLAY_COUNT)))
.into();
}
let video = context.video().expect("Error initializing SDL video");
let mut window = video
.window("Practical runner", 480, window_height)
.position_centered()
.borderless()
.build()
.expect("Error creating window");
window.set_opacity(0.0).unwrap();
let canvas = window.into_canvas().build().expect("Error creating canvas");
let mut cloned_executables = executables.clone();
cloned_executables.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
Self {
executables: cloned_executables,
context,
canvas,
input: String::from(""),
ttf,
}
}
pub fn run(&mut self) -> Option<String> {
let mut filtered_executables = self.executables.clone();
self.canvas
.set_draw_color(color_from_hex(BACKGROUND_COLOR).unwrap());
let font_path = String::from("/usr/share/fonts/OTF/GeistMonoNerdFontMono-Regular.otf");
let font = self
.ttf
.load_font(font_path.clone(), FONT_POINT_SIZE)
.expect(&format!("Error loading font {}", font_path));
let creator = self.canvas.texture_creator();
let mut event_pump = self.context.event_pump().unwrap();
'running: loop {
self.canvas.clear();
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => {
self.input = String::from("");
break 'running;
}
Event::KeyDown { keycode, .. } => {
if let Some(key) = keycode {
match key {
Keycode::Backspace => {
if !self.input.is_empty() {
self.input.pop();
filter_executables(
&self.input,
&self.executables,
&mut filtered_executables,
);
}
}
Keycode::Return => {
// TODO: Improve this
if filtered_executables.len() > 0 {
self.input = filtered_executables[0].clone();
}
break 'running;
}
_ => (),
}
}
}
Event::TextInput { text, .. } => {
self.input += &text;
filter_executables(
&self.input,
&self.executables,
&mut filtered_executables,
);
}
_ => {}
}
}
let font_color = color_from_hex(FONT_COLOR).expect("Error loading FONT_COLOR");
if !self.input.is_empty() {
let surface = font
.render(&self.input)
.blended(font_color)
.expect("Error rendering text");
let rect = Rect::new(
PADDING.into(),
PADDING.into(),
surface.width(),
surface.height(),
);
let texture = creator
.create_texture_from_surface(surface)
.expect("Error creating texture");
let _ = self.canvas.copy(&texture, None, Some(rect));
}
let item_count = MAX_ITEM_DISPLAY_COUNT.min(filtered_executables.len() as u16);
for i in 0..item_count {
let offset = PADDING + (font.height() as u16 + LINE_SPACING) * (i + 1);
let surface = font
.render(&filtered_executables[i as usize])
.blended(font_color)
.expect("Error rendering text");
let rect = Rect::new(
PADDING.into(),
offset.into(),
surface.width(),
surface.height(),
);
let texture = creator
.create_texture_from_surface(surface)
.expect("Error creating texture");
let _ = self.canvas.copy(&texture, None, Some(rect));
}
self.canvas.present();
std::thread::sleep(Duration::from_millis(8))
}
if self.input.is_empty() {
None
} else {
Some(self.input.clone())
}
}
}
fn filter_executables(
input: &String,
executables: &Vec<String>,
filtered_executables: &mut Vec<String>,
) {
*filtered_executables = executables
.iter()
.filter(|e| (*e).starts_with(input))
.map(|e| e.to_string())
.collect();
}

16
src/utils.rs Normal file
View File

@@ -0,0 +1,16 @@
use sdl2::pixels::Color;
#[derive(Debug, Clone)]
pub struct ColorParseError;
pub fn color_from_hex(hex: &str) -> Result<Color, ColorParseError> {
if hex.len() != 7 || hex.chars().nth(0).unwrap() != '#' {
return Err(ColorParseError);
}
let r = u8::from_str_radix(&hex[1..3], 16).or(Err(ColorParseError))?;
let g = u8::from_str_radix(&hex[3..5], 16).or(Err(ColorParseError))?;
let b = u8::from_str_radix(&hex[5..7], 16).or(Err(ColorParseError))?;
Ok(Color::RGB(r, g, b))
}