feat: a working run menu
This commit is contained in:
54
Cargo.lock
generated
54
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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
12
src/config.rs
Normal 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";
|
||||||
26
src/main.rs
26
src/main.rs
@@ -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
209
src/runner/mod.rs
Normal 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
16
src/utils.rs
Normal 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))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user