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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
@@ -109,6 +121,18 @@ version = "1.70.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.83"
|
||||
@@ -132,6 +156,30 @@ name = "runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"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]]
|
||||
@@ -163,6 +211,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -8,3 +8,4 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
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 executables::get_executables;
|
||||
use runner::Runner;
|
||||
|
||||
mod arguments;
|
||||
mod executables;
|
||||
mod runner;
|
||||
mod config;
|
||||
mod utils;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args = arguments::Arguments::parse();
|
||||
// let args = arguments::Arguments::parse();
|
||||
|
||||
let executables = get_executables()?;
|
||||
|
||||
let mut runner = Runner::new(executables);
|
||||
|
||||
if let Some(program) = runner.run() {
|
||||
run_program(program);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(program: impl ToString) {
|
||||
let _ = process::Command::new(program.to_string()).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).spawn();
|
||||
fn run_program(program: impl ToString) {
|
||||
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