added padding_pct
This commit is contained in:
parent
f8854de25c
commit
e2776996ef
File diff suppressed because it is too large
Load Diff
|
@ -17,11 +17,12 @@ pub enum Component {
|
|||
}
|
||||
|
||||
impl Component {
|
||||
fn debug(&self) -> String {
|
||||
#[cfg(test)]
|
||||
pub fn debug(&self) -> String {
|
||||
match self {
|
||||
Component::NextLine => "NextLine".to_string(),
|
||||
Component::X(x) => format!("X({})", x),
|
||||
Component::String(s) => format!("\"{}\"", s),
|
||||
Component::String(s) => format!("\"{}\"({})", s, s.len()),
|
||||
Component::Fg(c) => format!("FG({:#?})", c)
|
||||
.replace('\n', "")
|
||||
.replace(' ', ""),
|
||||
|
@ -564,7 +565,7 @@ mod tests {
|
|||
"Scrollable widget: no scrolling",
|
||||
Instruction::start()
|
||||
.fixed(10, vec![scrolling_widget]),
|
||||
(0..10)
|
||||
(90..100)
|
||||
.into_iter()
|
||||
.map(|num| {
|
||||
vec![
|
||||
|
@ -587,7 +588,7 @@ mod tests {
|
|||
(
|
||||
"Scrollable widget: scrolled by 3",
|
||||
Instruction::start().fixed(10, vec![scrolled_by_3]),
|
||||
(0..10)
|
||||
(87..97)
|
||||
.into_iter()
|
||||
.map(|num| {
|
||||
vec![
|
||||
|
|
|
@ -166,10 +166,14 @@ impl PlanState {
|
|||
action: Action,
|
||||
scr: &mut Screen,
|
||||
) -> Result<Self> {
|
||||
let term_size = termion::terminal_size()?;
|
||||
let mut new = match action {
|
||||
Action::ReplaceAll(layers) => {
|
||||
Self::from_plans(layers, self.base_colorset)?
|
||||
}
|
||||
Action::ReplaceAll(layers) => Self::new(
|
||||
layers,
|
||||
self.base_colorset,
|
||||
term_size,
|
||||
self.last_render,
|
||||
),
|
||||
Action::ReplaceLayer(layer, index) => {
|
||||
if index >= self.plans.len() {
|
||||
return Err(anyhow::anyhow!(
|
||||
|
@ -184,12 +188,22 @@ impl PlanState {
|
|||
Action::PushLayer(layer) => {
|
||||
let mut layers = self.plans;
|
||||
layers.push(layer);
|
||||
Self::from_plans(layers, self.base_colorset)?
|
||||
Self::new(
|
||||
layers,
|
||||
self.base_colorset,
|
||||
term_size,
|
||||
self.last_render,
|
||||
)
|
||||
}
|
||||
Action::PopLayer => {
|
||||
let mut layers = self.plans;
|
||||
layers.pop();
|
||||
Self::from_plans(layers, self.base_colorset)?
|
||||
Self::new(
|
||||
layers,
|
||||
self.base_colorset,
|
||||
term_size,
|
||||
self.last_render,
|
||||
)
|
||||
}
|
||||
Action::Nothing => return Ok(self),
|
||||
};
|
||||
|
@ -215,6 +229,9 @@ where
|
|||
let mut plans =
|
||||
PlanState::from_plans(view.init()?, base_colorset)?;
|
||||
plans.render(&mut screen)?;
|
||||
// FIXME: current loop means that pasting a string with len >1
|
||||
// results in only the first character being rendered, until
|
||||
// something else triggers a render
|
||||
loop {
|
||||
if let Some(msg) = view.query() {
|
||||
plans = plans.act_on(
|
||||
|
|
|
@ -5,6 +5,7 @@ pub enum Token {
|
|||
Centered(Box<Token>),
|
||||
Limited(Box<Token>, usize),
|
||||
CharPad(Box<Token>, char, usize),
|
||||
PadPercent(Box<Token>, u8),
|
||||
Fg(Box<Token>, Color),
|
||||
Bg(Box<Token>, Color),
|
||||
Link(Box<Token>, String),
|
||||
|
@ -51,18 +52,41 @@ impl Token {
|
|||
Self::CharPad(Box::new(self), ' ', pad_to)
|
||||
}
|
||||
|
||||
pub fn str_len(&self) -> usize {
|
||||
match self {
|
||||
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) => {
|
||||
(*pad_to).max(t.str_len())
|
||||
pub fn pad_percent(self, pct: u8) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
if pct > 100 {
|
||||
panic!("pad_percent with >100% width: {}", pct);
|
||||
}
|
||||
Self::PadPercent(Box::new(self), 100.min(pct))
|
||||
}
|
||||
|
||||
// Returns (str_count, str_len_total)
|
||||
pub fn str_len(&self, total_width: usize) -> (usize, usize) {
|
||||
match self {
|
||||
Token::String(t, s) => {
|
||||
let (count, len) = t.str_len(total_width);
|
||||
(count + 1, len + s.len())
|
||||
}
|
||||
Token::Fg(t, _) => t.str_len(total_width),
|
||||
Token::Bg(t, _) => t.str_len(total_width),
|
||||
Token::Link(t, _) => t.str_len(total_width),
|
||||
Token::Centered(t) => t.str_len(total_width),
|
||||
Token::Limited(t, lim) => {
|
||||
let (count, len) = t.str_len(total_width);
|
||||
(count, (*lim).min(len))
|
||||
}
|
||||
Token::CharPad(t, _, pad_to) => {
|
||||
let (count, len) = t.str_len(total_width);
|
||||
(count, (*pad_to).max(len))
|
||||
}
|
||||
Token::End => (0, 0),
|
||||
Token::PadPercent(t, pct) => {
|
||||
let (count, len) = t.str_len(total_width);
|
||||
(
|
||||
count,
|
||||
((*pct as usize * total_width) / 100).max(len),
|
||||
)
|
||||
}
|
||||
Token::End => 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,6 +102,7 @@ impl Token {
|
|||
Token::Link(next, _) => next.has_centered(),
|
||||
Token::String(next, _) => next.has_centered(),
|
||||
Token::End => false,
|
||||
Token::PadPercent(next, _) => next.has_centered(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,13 +138,13 @@ impl Token {
|
|||
.chain(t.with_width(width))
|
||||
.collect(),
|
||||
Token::Link(t, name) => {
|
||||
vec![Component::Link(name, t.str_len())]
|
||||
vec![Component::Link(name, t.str_len(width).1)]
|
||||
.into_iter()
|
||||
.chain(t.with_width(width))
|
||||
.collect()
|
||||
}
|
||||
Token::Centered(cnt) => {
|
||||
let mut str_len = cnt.str_len();
|
||||
let mut str_len = cnt.str_len(width).1;
|
||||
let components = if str_len > width {
|
||||
str_len = width;
|
||||
cnt.limited(width).with_width(width)
|
||||
|
@ -170,6 +195,43 @@ impl Token {
|
|||
.chain(t.with_width(width))
|
||||
.collect(),
|
||||
Token::End => vec![],
|
||||
Token::PadPercent(next, pct) => {
|
||||
// FIXME: I feel like this'll be a problem
|
||||
// at some point but I cba to deal with it rn
|
||||
let (str_cnt, str_len) = next.str_len(width);
|
||||
let actual_pad = (pct as usize * width) / 100;
|
||||
let mut str_idx = 0;
|
||||
next.with_width(width)
|
||||
.into_iter()
|
||||
.map(|comp| {
|
||||
if let Component::String(s) = comp {
|
||||
str_idx += 1;
|
||||
if str_idx != str_cnt {
|
||||
// Continue as this isn't the end
|
||||
Component::String(s)
|
||||
} else if str_len >= actual_pad {
|
||||
// FIXME: this is probably THE BAD
|
||||
Component::String({
|
||||
let mut s = s;
|
||||
s.truncate(
|
||||
actual_pad
|
||||
- (str_len - s.len()),
|
||||
);
|
||||
s
|
||||
})
|
||||
} else {
|
||||
Component::String(format!(
|
||||
"{}{}",
|
||||
s,
|
||||
" ".repeat(actual_pad - str_len)
|
||||
))
|
||||
}
|
||||
} else {
|
||||
comp
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +242,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_token_gen() {
|
||||
const WIDTH: usize = 20;
|
||||
const WIDTH: usize = 100;
|
||||
vec![
|
||||
(
|
||||
"string -> string",
|
||||
|
@ -281,6 +343,48 @@ mod tests {
|
|||
Component::String("learn more".into()),
|
||||
],
|
||||
),
|
||||
(
|
||||
"padding percentage",
|
||||
Token::text("this gets longer").pad_percent(100),
|
||||
vec![Component::String(
|
||||
"this gets longer".to_string()
|
||||
+ &" "
|
||||
.repeat(WIDTH - "this gets longer".len()),
|
||||
)],
|
||||
),
|
||||
(
|
||||
"pad percent with centering",
|
||||
Token::text("all to the middle")
|
||||
.pad_percent(60)
|
||||
.centered(),
|
||||
vec![
|
||||
Component::X((WIDTH - (WIDTH * 60) / 100) / 2),
|
||||
Component::String(
|
||||
"all to the middle".to_string()
|
||||
+ &" ".repeat(
|
||||
((WIDTH * 60) / 100)
|
||||
- "all to the middle".len(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
"two strings, padded pct",
|
||||
Token::text("two")
|
||||
.bg(Color::BLUE)
|
||||
.string("one")
|
||||
.pad_percent(80)
|
||||
.centered(),
|
||||
vec![
|
||||
Component::X((WIDTH - ((WIDTH * 80) / 100)) / 2),
|
||||
Component::String("one".into()),
|
||||
Component::Bg(Color::BLUE),
|
||||
Component::String(
|
||||
"two".to_string()
|
||||
+ &" ".repeat(((WIDTH * 80) / 100) - 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|(name, token, expected)| {
|
||||
|
@ -298,12 +402,12 @@ mod tests {
|
|||
assert!(
|
||||
exp == *act,
|
||||
"<{}>: component at index {} mismatch.
|
||||
expected:\n{:#?}
|
||||
actual:\n{:#?}",
|
||||
expected:\n{}
|
||||
actual:\n{}",
|
||||
&name,
|
||||
i,
|
||||
exp,
|
||||
act,
|
||||
exp.debug(),
|
||||
act.debug(),
|
||||
);
|
||||
}
|
||||
})
|
||||
|
|
|
@ -21,7 +21,11 @@ impl From<termion::event::Key> for Key {
|
|||
termion::event::Key::Delete => Self::Delete,
|
||||
termion::event::Key::Insert => Self::Insert,
|
||||
termion::event::Key::F(f) => Self::F(f),
|
||||
termion::event::Key::Char(c) => Self::Char(c),
|
||||
termion::event::Key::Char(c) => match c {
|
||||
'\n' => Self::Return,
|
||||
'\t' => Self::Tab,
|
||||
_ => Self::Char(c),
|
||||
},
|
||||
termion::event::Key::Alt(c) => Self::Alt(c),
|
||||
termion::event::Key::Ctrl(c) => Self::Ctrl(c),
|
||||
termion::event::Key::Null => Self::Null,
|
||||
|
@ -51,4 +55,6 @@ pub enum Key {
|
|||
Ctrl(char),
|
||||
Null,
|
||||
Esc,
|
||||
Return,
|
||||
Tab,
|
||||
}
|
||||
|
|
|
@ -8,11 +8,13 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
kkdisp = { version = "0.1.0", path = "../kkdisp" }
|
||||
futures = "0.3.25"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.24.2"
|
||||
features = ["full"]
|
||||
|
||||
[dependencies.misskey]
|
||||
version = "*"
|
||||
git = "https://github.com/coord-e/misskey-rs"
|
||||
branch = "develop"
|
||||
features = ["websocket-client"]
|
||||
|
|
105
kkx/src/login.rs
105
kkx/src/login.rs
|
@ -1,3 +1,4 @@
|
|||
use futures::stream::TryStreamExt;
|
||||
use kkdisp::{
|
||||
component::{Plan, Widget},
|
||||
theme::Color,
|
||||
|
@ -5,6 +6,8 @@ use kkdisp::{
|
|||
view::{Event, Key},
|
||||
Action,
|
||||
};
|
||||
use misskey::{StreamingClientExt, WebSocketClient};
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
|
||||
use crate::Message;
|
||||
pub enum LoginFocus {
|
||||
|
@ -27,15 +30,7 @@ pub struct LoginPrompt {
|
|||
token: String,
|
||||
error: Option<String>,
|
||||
focus: LoginFocus,
|
||||
}
|
||||
|
||||
impl From<&LoginPrompt> for Plan {
|
||||
fn from(value: &LoginPrompt) -> Self {
|
||||
Plan::start()
|
||||
.fill(vec![])
|
||||
.fixed(8, vec![Widget::new(100, value.tokens())])
|
||||
.fill(vec![])
|
||||
}
|
||||
client: Option<WebSocketClient>,
|
||||
}
|
||||
|
||||
impl LoginPrompt {
|
||||
|
@ -45,9 +40,17 @@ impl LoginPrompt {
|
|||
token: String::new(),
|
||||
error: None,
|
||||
focus: LoginFocus::Hostname,
|
||||
client: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plan(&self) -> Plan {
|
||||
Plan::start()
|
||||
.fill(vec![])
|
||||
.fixed(8, vec![Widget::new(100, self.tokens())])
|
||||
.fill(vec![])
|
||||
}
|
||||
|
||||
fn backspace(&mut self) {
|
||||
match self.focus {
|
||||
LoginFocus::Hostname => {
|
||||
|
@ -59,11 +62,26 @@ impl LoginPrompt {
|
|||
LoginFocus::OK => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attempt(&self) -> Result<(), anyhow::Error> {
|
||||
pub fn query(&mut self) -> Option<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn attempt(&mut self) {
|
||||
let hostname = self.hostname.clone();
|
||||
if hostname.len() == 0 {
|
||||
self.error = Some("empty hostname".into());
|
||||
self.focus = LoginFocus::Hostname;
|
||||
return;
|
||||
}
|
||||
let token = self.token.clone();
|
||||
if token.len() == 0 {
|
||||
self.error = Some("empty token".into());
|
||||
self.focus = LoginFocus::Token;
|
||||
return;
|
||||
}
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
event: Event<Message>,
|
||||
|
@ -71,13 +89,29 @@ impl LoginPrompt {
|
|||
match event {
|
||||
Event::Link(lnk) => {
|
||||
if lnk == "ok" {
|
||||
todo!()
|
||||
self.attempt();
|
||||
Ok(Action::ReplaceAll(vec![self.plan()]))
|
||||
} else if lnk == "token" {
|
||||
self.focus = LoginFocus::Token;
|
||||
Ok(Action::ReplaceAll(vec![self.plan()]))
|
||||
} else if lnk == "hostname" {
|
||||
self.focus = LoginFocus::Hostname;
|
||||
Ok(Action::ReplaceAll(vec![self.plan()]))
|
||||
} else {
|
||||
Ok(Action::Nothing)
|
||||
}
|
||||
}
|
||||
Event::Input(input) => {
|
||||
match input {
|
||||
Key::Return => match self.focus {
|
||||
LoginFocus::Hostname => {
|
||||
self.focus = LoginFocus::Token;
|
||||
}
|
||||
LoginFocus::Token => {
|
||||
self.focus = LoginFocus::OK;
|
||||
}
|
||||
LoginFocus::OK => self.attempt(),
|
||||
},
|
||||
Key::Backspace => self.backspace(),
|
||||
Key::Up => {
|
||||
self.focus = match self.focus {
|
||||
|
@ -93,24 +127,11 @@ impl LoginPrompt {
|
|||
LoginFocus::OK => LoginFocus::Hostname,
|
||||
};
|
||||
}
|
||||
Key::Char(c) => {
|
||||
if c == '\n' {
|
||||
if self.focus.ok() {
|
||||
self.attempt()?;
|
||||
}
|
||||
} else if c == '\t' {
|
||||
} else {
|
||||
match self.focus {
|
||||
LoginFocus::Hostname => {
|
||||
self.hostname.push(c)
|
||||
}
|
||||
LoginFocus::Token => {
|
||||
self.token.push(c)
|
||||
}
|
||||
Key::Char(c) => match self.focus {
|
||||
LoginFocus::Hostname => self.hostname.push(c),
|
||||
LoginFocus::Token => self.token.push(c),
|
||||
LoginFocus::OK => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Key::Ctrl(c) => {
|
||||
if c == 'u' || c == 'U' {
|
||||
match self.focus {
|
||||
|
@ -131,16 +152,17 @@ impl LoginPrompt {
|
|||
return Ok(Action::Nothing);
|
||||
}
|
||||
};
|
||||
Ok(Action::ReplaceAll(vec![(&*self).into()]))
|
||||
Ok(Action::ReplaceAll(vec![self.plan()]))
|
||||
}
|
||||
Event::Message(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tokens(&self) -> Vec<Token> {
|
||||
let mut hostname =
|
||||
Token::text(self.hostname.clone()).padded(10);
|
||||
let mut token = Token::text(self.token.clone()).padded(10);
|
||||
const HOSTNAME_PROMPT: &str = "hostname: ";
|
||||
const TOKEN_PROMPT: &str = "token: ";
|
||||
let mut hostname = Token::text(self.hostname.clone());
|
||||
let mut token = Token::text(self.token.clone());
|
||||
let mut ok = Token::text("[ok]");
|
||||
match self.focus {
|
||||
LoginFocus::Hostname => {
|
||||
|
@ -156,11 +178,22 @@ impl LoginPrompt {
|
|||
.centered(),
|
||||
Token::End,
|
||||
match &self.error {
|
||||
Some(e) => Token::text(e).fg(Color::RED),
|
||||
Some(e) => Token::text(e)
|
||||
.fg(Color::RED)
|
||||
.pad_percent(80)
|
||||
.centered(),
|
||||
None => Token::End,
|
||||
},
|
||||
hostname.string("hostname: ").centered(),
|
||||
token.string("token: ").centered(),
|
||||
hostname
|
||||
.string(HOSTNAME_PROMPT)
|
||||
.link("hostname")
|
||||
.pad_percent(80)
|
||||
.centered(),
|
||||
token
|
||||
.string(TOKEN_PROMPT)
|
||||
.link("token")
|
||||
.pad_percent(80)
|
||||
.centered(),
|
||||
ok.link("ok").centered(),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ pub enum Page {
|
|||
impl Page {
|
||||
fn init(&self) -> Result<PlanLayers, anyhow::Error> {
|
||||
match self {
|
||||
Page::Login(log) => Ok(vec![log.into()]),
|
||||
Page::Login(log) => Ok(vec![log.plan()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue