added clicking (working!)

This commit is contained in:
emilis 2023-01-23 00:18:55 +00:00
parent 1960123e3f
commit 9471d47ade
7 changed files with 576 additions and 54 deletions

251
Cargo.lock generated
View File

@ -8,12 +8,39 @@ version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "kkdisp"
version = "0.1.0"
@ -29,6 +56,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"kkdisp",
"tokio",
]
[[package]]
@ -37,12 +65,88 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mio"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.50"
@ -79,6 +183,12 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
@ -99,6 +209,31 @@ dependencies = [
"syn",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.107"
@ -122,8 +257,124 @@ dependencies = [
"redox_termios",
]
[[package]]
name = "tokio"
version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

View File

@ -9,6 +9,7 @@ use std::io::{Stdout, Write};
#[derive(Eq, Clone, Debug)]
pub enum Component {
NextLine,
Link(String, usize),
X(usize),
String(String),
Fg(Color),
@ -27,46 +28,27 @@ impl Component {
Component::Bg(c) => format!("BG({:#?})", c)
.replace('\n', "")
.replace(' ', ""),
Component::Link(name, ln) => {
format!("Clickable[{}:{}]", name, ln)
}
}
}
}
pub trait Cursed {
fn scene(
&mut self,
comp: Vec<Component>,
) -> Result<(), anyhow::Error>;
pub struct LineArea {
pos: (u16, u16),
length: u16,
}
impl Cursed for RawTerminal<Stdout> {
fn scene(
&mut self,
comp: Vec<Component>,
) -> Result<(), anyhow::Error> {
let mut line = 1;
let screen_str = comp
.into_iter()
.map(|c| match c {
Component::NextLine => {
line += 1;
String::from("\r\n")
impl LineArea {
pub fn new(pos: (u16, u16), length: u16) -> Self {
Self { pos, length }
}
Component::X(x) => {
termion::cursor::Goto((x + 1) as u16, line).into()
}
Component::String(s) => s,
Component::Fg(c) => c.fg(),
Component::Bg(c) => c.bg(),
})
.collect::<String>();
write!(
self,
"{clear}{start}{scene}",
clear = termion::clear::All,
start = termion::cursor::Goto(1, 1),
scene = screen_str,
)?;
Ok(())
pub fn matches(&self, pos: (u16, u16)) -> bool {
self.pos.1 == pos.1
&& pos.0 >= self.pos.0
&& pos.0 <= (self.pos.0 + self.length - 1)
}
}
@ -93,6 +75,12 @@ impl PartialEq for Component {
Self::NextLine => true,
_ => false,
},
Self::Link(name, length) => match other {
Self::Link(other_name, other_length) => {
name == other_name && length == other_length
}
_ => false,
},
}
}
}

View File

@ -1,27 +1,200 @@
use crate::component::Cursed;
use component::{Plan, Widget};
use component::{Component, LineArea, Plan, Widget};
use std::{
io::{Stdout, Write},
time::Duration,
};
use termion::raw::{IntoRawMode, RawTerminal};
use termion::{
clear, cursor,
input::{MouseTerminal, TermRead},
raw::{IntoRawMode, RawTerminal},
screen::{AlternateScreen, IntoAlternateScreen},
AsyncReader,
};
use theme::{Color, ColorSet};
use token::Token;
use view::Event;
type Result<T> = std::result::Result<T, anyhow::Error>;
type Screen = MouseTerminal<RawTerminal<AlternateScreen<Stdout>>>;
extern crate termion;
mod component;
mod theme;
mod token;
pub mod component;
pub mod theme;
pub mod token;
pub mod view;
pub struct Display {
// needs to hold the termion display
screen: RawTerminal<Stdout>,
}
pub async fn run<V>(view: V) -> Result<()>
where
V: View,
{
let mut view = view;
let mut screen: Screen = MouseTerminal::from(
std::io::stdout().into_alternate_screen()?.into_raw_mode()?,
);
let mut events_iter = termion::async_stdin().events();
let mut plans = view.init()?;
let (term_w, term_h) = termion::terminal_size()?;
let mut layers = make_layers(
plans,
ColorSet {
fg: Color::WHITE,
bg: Color::BLACK,
},
(term_w as usize, term_h as usize),
);
render_layers(&layers, &mut screen)?;
screen.flush()?;
loop {
let event = match events_iter.next() {
Some(e) => e,
None => {
std::thread::sleep(Duration::from_millis(50));
continue;
}
}?;
impl Display {
pub fn new() -> Result<Self, anyhow::Error> {
Ok(Self {
screen: std::io::stdout().into_raw_mode()?,
})
let event = match event {
termion::event::Event::Mouse(mus) => match mus {
termion::event::MouseEvent::Press(_, x, y) => {
// TODO: scrolling later
match match_click(&layers, (x, y)) {
Some(yay) => Event::Link(yay),
None => continue,
}
}
_ => Event::Input(event),
},
_ => Event::Input(event),
};
plans = view.update(event)?;
let (term_w, term_h) = termion::terminal_size()?;
layers = make_layers(
plans,
ColorSet {
fg: Color::WHITE,
bg: Color::BLACK,
},
(term_w as usize, term_h as usize),
);
write!(screen, "{}", clear::All)?;
render_layers(&layers, &mut screen)?;
screen.flush()?;
}
}
fn match_click(
layers: &Vec<Layer>,
click: (u16, u16),
) -> Option<String> {
for layer in layers.into_iter().rev() {
for link in &layer.links {
if link.1.matches(click) {
return Some(link.0.clone());
}
}
}
None
}
fn make_layers(
plans: PlanLayers,
base_colorset: ColorSet,
term_dimensions: (usize, usize),
) -> Vec<Layer> {
plans
.into_iter()
.map(|plan| Layer::make(plan, base_colorset, term_dimensions))
.collect()
}
fn render_layers(
layered: &Vec<Layer>,
scr: &mut Screen,
) -> Result<()> {
for layer in layered.into_iter().map(|layer| {
let mut line = 1;
(&layer.components)
.into_iter()
.map(|component| match component {
Component::NextLine => {
line += 1;
"\n\r".into()
}
Component::Link(_, _) => panic!(
"links should be filtered out at the layer"
),
Component::X(x) => {
cursor::Goto((x + 1) as u16, line).into()
}
Component::String(s) => s.clone(),
Component::Fg(c) => c.fg(),
Component::Bg(c) => c.bg(),
})
.collect::<String>()
}) {
write!(scr, "{}", layer)?;
}
Ok(())
}
pub struct Layer {
links: Vec<(String, LineArea)>,
components: Vec<Component>,
}
impl Layer {
pub fn make(
plan: Plan,
base_colorset: ColorSet,
term_dimensions: (usize, usize),
) -> Self {
let mut line = 1;
let mut line_offset = 1;
let mut links = Vec::new();
let components = plan
.make(base_colorset, term_dimensions)
.into_iter()
.filter(|comp| match comp {
Component::NextLine => {
line += 1;
line_offset = 1;
true
}
Component::X(x) => {
line_offset = (x + 1) as u16;
true
}
Component::String(s) => {
line_offset += s.len() as u16;
true
}
Component::Link(name, length) => {
links.push((
name.clone(),
LineArea::new(
(line_offset, line),
*length as u16,
),
));
false
}
_ => true,
})
.collect();
Self { links, components }
}
}
// Simple type alias to be clear that the plans are going to
// be layered on top of eachother
pub type PlanLayers = Vec<Plan>;
pub trait View {
fn init(
&mut self,
) -> std::result::Result<PlanLayers, anyhow::Error>;
fn update(
&mut self,
event: Event,
) -> std::result::Result<PlanLayers, anyhow::Error>;
}

View File

@ -7,6 +7,7 @@ pub enum Token {
CharPad(Box<Token>, char, usize),
Fg(Box<Token>, Color),
Bg(Box<Token>, Color),
Link(Box<Token>, String),
String(Box<Token>, String),
End,
}
@ -55,6 +56,7 @@ impl Token {
Token::String(t, s) => s.len() + t.str_len(),
Token::Fg(t, _) => t.str_len(),
Token::Bg(t, _) => t.str_len(),
Token::Link(t, _) => t.str_len(),
Token::Centered(t) => t.str_len(),
Token::Limited(t, lim) => (*lim).min(t.str_len()),
Token::CharPad(t, _, pad_to) => {
@ -64,6 +66,13 @@ impl Token {
}
}
pub fn link<S>(self, name: S) -> Self
where
S: Into<String>,
{
Self::Link(Box::new(self), name.into())
}
pub fn pad_char(self, c: char, pad_to: usize) -> Self {
Self::CharPad(Box::new(self), c, pad_to)
}
@ -82,6 +91,12 @@ impl Token {
.into_iter()
.chain(t.with_width(width))
.collect(),
Token::Link(t, name) => {
vec![Component::Link(name, t.str_len())]
.into_iter()
.chain(t.with_width(width))
.collect()
}
Token::Centered(cnt) => {
let mut str_len = cnt.str_len();
let components = if str_len > width {
@ -234,6 +249,17 @@ mod tests {
Component::String("end".into()),
],
),
(
"contains a link",
Token::text("learn more")
.centered()
.link("string_link"),
vec![
Component::Link("string_link".into(), 10),
Component::X((WIDTH - 10) / 2),
Component::String("learn more".into()),
],
),
]
.into_iter()
.for_each(|(name, token, expected)| {

9
kkdisp/src/view.rs Normal file
View File

@ -0,0 +1,9 @@
use crate::{
component::{Component, LineArea, Plan},
theme::ColorSet,
};
pub enum Event {
Link(String),
Input(termion::event::Event),
}

View File

@ -8,3 +8,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.68"
kkdisp = { version = "0.1.0", path = "../kkdisp" }
[dependencies.tokio]
version = "1.24.2"
features = ["full"]

View File

@ -1,7 +1,78 @@
use kkdisp::Display;
use kkdisp::{
component::{Plan, Widget},
theme::Color,
token::Token,
view::Event,
View,
};
fn main() {
// let disp = Display::new();
// let widget_idk = Widget::new()
Display::test();
#[tokio::main]
async fn main() {
kkdisp::run(App::default()).await.unwrap()
}
pub struct App {
states: Vec<Plan>,
index: usize,
}
impl Default for App {
fn default() -> Self {
Self {
index: 0,
states: vec![
Plan::start().fill(vec![]).fixed(
4,
vec![Widget::new(
kkdisp::component::SectionWidth::Full,
vec![
Token::End,
Token::text("click me")
.bg(Color::BLUE)
.link("click"),
],
)],
),
Plan::start().fill(vec![]).fixed(
4,
vec![Widget::new(
kkdisp::component::SectionWidth::Full,
vec![
Token::End,
Token::text(
"you did it! now undo it u fuck",
)
.bg(Color::RED)
.link("click"),
],
)],
),
],
}
}
}
impl View for App {
fn init(
&mut self,
) -> std::result::Result<kkdisp::PlanLayers, anyhow::Error> {
Ok(vec![self.states[self.index].clone()])
}
fn update(
&mut self,
event: Event,
) -> std::result::Result<kkdisp::PlanLayers, anyhow::Error> {
match event {
Event::Link(lnk) => {
if lnk == "click" {
self.index ^= 1;
Ok(vec![self.states[self.index].clone()])
} else {
panic!("bad link");
}
}
_ => Ok(vec![self.states[self.index].clone()]),
}
}
}