render: add a damage visualizer
This commit is contained in:
parent
3f4a677d0c
commit
76a3c50560
18 changed files with 625 additions and 90 deletions
36
src/cli/color.rs
Normal file
36
src/cli/color.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use {
|
||||
crate::{theme::Color, utils::errorfmt::ErrorFmt},
|
||||
std::ops::Range,
|
||||
};
|
||||
|
||||
pub fn parse_color(string: &str) -> Color {
|
||||
let hex = match string.strip_prefix("#") {
|
||||
Some(s) => s,
|
||||
_ => fatal!("Color must start with #"),
|
||||
};
|
||||
let d = |range: Range<usize>| match u8::from_str_radix(&hex[range.clone()], 16) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
fatal!(
|
||||
"Could not parse color component {}: {}",
|
||||
&hex[range],
|
||||
ErrorFmt(e)
|
||||
)
|
||||
}
|
||||
};
|
||||
let s = |range: Range<usize>| {
|
||||
let v = d(range);
|
||||
(v << 4) | v
|
||||
};
|
||||
let (r, g, b, a) = match hex.len() {
|
||||
3 => (s(0..1), s(1..2), s(2..3), u8::MAX),
|
||||
4 => (s(0..1), s(1..2), s(2..3), s(3..4)),
|
||||
6 => (d(0..2), d(2..4), d(4..6), u8::MAX),
|
||||
8 => (d(0..2), d(2..4), d(4..6), d(6..8)),
|
||||
_ => fatal!(
|
||||
"Unexpected length of color string (should be 3, 4, 6, or 8): {}",
|
||||
hex.len()
|
||||
),
|
||||
};
|
||||
jay_config::theme::Color::new_straight(r, g, b, a).into()
|
||||
}
|
||||
106
src/cli/damage_tracking.rs
Normal file
106
src/cli/damage_tracking.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::{color::parse_color, duration::parse_duration, GlobalArgs},
|
||||
tools::tool_client::{with_tool_client, ToolClient},
|
||||
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
|
||||
},
|
||||
clap::{Args, Subcommand},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct DamageTrackingArgs {
|
||||
#[clap(subcommand)]
|
||||
pub command: DamageTrackingCmd,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum DamageTrackingCmd {
|
||||
/// Visualize damage.
|
||||
Show,
|
||||
/// Hide damage.
|
||||
Hide,
|
||||
/// Set the color used for damage visualization.
|
||||
SetColor(ColorArgs),
|
||||
/// Set the amount of time damage is shown.
|
||||
SetDecay(DecayArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ColorArgs {
|
||||
/// The color to visualize damage.
|
||||
///
|
||||
/// Should be specified in one of the following formats:
|
||||
///
|
||||
/// * `#rgb`
|
||||
/// * `#rgba`
|
||||
/// * `#rrggbb`
|
||||
/// * `#rrggbbaa`
|
||||
pub color: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct DecayArgs {
|
||||
/// The interval of inactivity after which to disable the screens.
|
||||
///
|
||||
/// Minutes, seconds, and milliseconds can be specified in any of the following formats:
|
||||
///
|
||||
/// * 1m
|
||||
/// * 1m5s
|
||||
/// * 1m 5s
|
||||
/// * 1min 5sec
|
||||
/// * 1 minute 5 seconds.
|
||||
pub duration: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn main(global: GlobalArgs, damage_tracking_args: DamageTrackingArgs) {
|
||||
with_tool_client(global.log_level.into(), |tc| async move {
|
||||
let damage_tracking = Rc::new(DamageTracking { tc: tc.clone() });
|
||||
damage_tracking.run(damage_tracking_args).await;
|
||||
});
|
||||
}
|
||||
|
||||
struct DamageTracking {
|
||||
tc: Rc<ToolClient>,
|
||||
}
|
||||
|
||||
impl DamageTracking {
|
||||
async fn run(&self, args: DamageTrackingArgs) {
|
||||
let tc = &self.tc;
|
||||
let Some(dt) = tc.jay_damage_tracking().await else {
|
||||
fatal!("Compositor does not support damage tracking");
|
||||
};
|
||||
match args.command {
|
||||
DamageTrackingCmd::Show => {
|
||||
tc.send(SetVisualizerEnabled {
|
||||
self_id: dt,
|
||||
enabled: 1,
|
||||
});
|
||||
}
|
||||
DamageTrackingCmd::Hide => {
|
||||
tc.send(SetVisualizerEnabled {
|
||||
self_id: dt,
|
||||
enabled: 0,
|
||||
});
|
||||
}
|
||||
DamageTrackingCmd::SetColor(c) => {
|
||||
let color = parse_color(&c.color);
|
||||
tc.send(SetVisualizerColor {
|
||||
self_id: dt,
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b,
|
||||
a: color.a,
|
||||
});
|
||||
}
|
||||
DamageTrackingCmd::SetDecay(c) => {
|
||||
let duration = parse_duration(&c.duration);
|
||||
tc.send(SetVisualizerDecay {
|
||||
self_id: dt,
|
||||
millis: duration.as_millis() as _,
|
||||
});
|
||||
}
|
||||
}
|
||||
tc.round_trip().await;
|
||||
}
|
||||
}
|
||||
102
src/cli/duration.rs
Normal file
102
src/cli/duration.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use {
|
||||
crate::utils::errorfmt::ErrorFmt,
|
||||
std::{collections::VecDeque, str::FromStr, time::Duration},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Component {
|
||||
Number(u64),
|
||||
Minutes(String),
|
||||
Seconds(String),
|
||||
Milliseconds(String),
|
||||
}
|
||||
|
||||
pub fn parse_duration(args: &[String]) -> Duration {
|
||||
let comp = parse_components(args);
|
||||
let mut minutes = None;
|
||||
let mut seconds = None;
|
||||
let mut milliseconds = None;
|
||||
let mut pending_num = None;
|
||||
for comp in comp {
|
||||
match comp {
|
||||
Component::Number(_) if pending_num.is_some() => {
|
||||
fatal!("missing number unit after {}", pending_num.unwrap())
|
||||
}
|
||||
Component::Number(n) => pending_num = Some(n),
|
||||
|
||||
Component::Minutes(n) if pending_num.is_none() => {
|
||||
fatal!("`{}` must be preceded by a number", n)
|
||||
}
|
||||
Component::Minutes(_) if minutes.is_some() => {
|
||||
fatal!("minutes specified multiple times")
|
||||
}
|
||||
Component::Minutes(_) => minutes = pending_num.take(),
|
||||
|
||||
Component::Seconds(n) if pending_num.is_none() => {
|
||||
fatal!("`{}` must be preceded by a number", n)
|
||||
}
|
||||
Component::Seconds(_) if seconds.is_some() => {
|
||||
fatal!("seconds specified multiple times")
|
||||
}
|
||||
Component::Seconds(_) => seconds = pending_num.take(),
|
||||
Component::Milliseconds(n) if pending_num.is_none() => {
|
||||
fatal!("`{}` must be preceded by a number", n)
|
||||
}
|
||||
Component::Milliseconds(_) if milliseconds.is_some() => {
|
||||
fatal!("milliseconds specified multiple times")
|
||||
}
|
||||
Component::Milliseconds(_) => milliseconds = pending_num.take(),
|
||||
}
|
||||
}
|
||||
if pending_num.is_some() {
|
||||
fatal!("missing number unit after {}", pending_num.unwrap());
|
||||
}
|
||||
if minutes.is_none() && seconds.is_none() && milliseconds.is_none() {
|
||||
fatal!("duration must be specified");
|
||||
}
|
||||
let mut ms = minutes.unwrap_or(0) as u128 * 60 * 1000
|
||||
+ seconds.unwrap_or(0) as u128 * 1000
|
||||
+ milliseconds.unwrap_or(0) as u128;
|
||||
if ms > u64::MAX as u128 {
|
||||
ms = u64::MAX as u128;
|
||||
}
|
||||
Duration::from_millis(ms as u64)
|
||||
}
|
||||
|
||||
fn parse_components(args: &[String]) -> Vec<Component> {
|
||||
let mut args = VecDeque::from_iter(args.iter().map(|s| s.to_ascii_lowercase()));
|
||||
let mut res = vec![];
|
||||
while let Some(arg) = args.pop_front() {
|
||||
if arg.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut arg = &arg[..];
|
||||
if is_num(arg.as_bytes()[0]) {
|
||||
if let Some(pos) = arg.as_bytes().iter().position(|&a| !is_num(a)) {
|
||||
args.push_front(arg[pos..].to_string());
|
||||
arg = &arg[..pos];
|
||||
}
|
||||
match u64::from_str(arg) {
|
||||
Ok(n) => res.push(Component::Number(n)),
|
||||
Err(e) => fatal!("Could not parse `{}` as a number: {}", arg, ErrorFmt(e)),
|
||||
}
|
||||
} else {
|
||||
if let Some(pos) = arg.as_bytes().iter().position(|&a| is_num(a)) {
|
||||
args.push_front(arg[pos..].to_string());
|
||||
arg = &arg[..pos];
|
||||
}
|
||||
let comp = match arg {
|
||||
"minutes" | "minute" | "min" | "m" => Component::Minutes(arg.to_string()),
|
||||
"seconds" | "second" | "sec" | "s" => Component::Seconds(arg.to_string()),
|
||||
"milliseconds" | "millisecond" | "ms" => Component::Milliseconds(arg.to_string()),
|
||||
_ => fatal!("Could not parse `{}`", arg),
|
||||
};
|
||||
res.push(comp);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn is_num(b: u8) -> bool {
|
||||
matches!(b, b'0'..=b'9')
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::{GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs},
|
||||
cli::{duration::parse_duration, GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs},
|
||||
tools::tool_client::{with_tool_client, Handle, ToolClient},
|
||||
utils::{errorfmt::ErrorFmt, stack::Stack},
|
||||
utils::stack::Stack,
|
||||
wire::{jay_compositor, jay_idle, JayIdleId, WlSurfaceId},
|
||||
},
|
||||
std::{cell::Cell, collections::VecDeque, rc::Rc, str::FromStr},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub fn main(global: GlobalArgs, args: IdleArgs) {
|
||||
|
|
@ -93,46 +93,11 @@ impl Idle {
|
|||
|
||||
async fn set(self, idle: JayIdleId, args: IdleSetArgs) {
|
||||
let tc = &self.tc;
|
||||
let interval;
|
||||
if args.interval.len() == 1 && args.interval[0] == "disabled" {
|
||||
interval = 0;
|
||||
let interval = if args.interval.len() == 1 && args.interval[0] == "disabled" {
|
||||
0
|
||||
} else {
|
||||
let comp = parse_components(&args.interval);
|
||||
let mut minutes = None;
|
||||
let mut seconds = None;
|
||||
let mut pending_num = None;
|
||||
for comp in comp {
|
||||
match comp {
|
||||
Component::Number(_) if pending_num.is_some() => {
|
||||
fatal!("missing number unit after {}", pending_num.unwrap())
|
||||
}
|
||||
Component::Number(n) => pending_num = Some(n),
|
||||
|
||||
Component::Minutes(n) if pending_num.is_none() => {
|
||||
fatal!("`{}` must be preceded by a number", n)
|
||||
}
|
||||
Component::Minutes(_) if minutes.is_some() => {
|
||||
fatal!("minutes specified multiple times")
|
||||
}
|
||||
Component::Minutes(_) => minutes = pending_num.take(),
|
||||
|
||||
Component::Seconds(n) if pending_num.is_none() => {
|
||||
fatal!("`{}` must be preceded by a number", n)
|
||||
}
|
||||
Component::Seconds(_) if seconds.is_some() => {
|
||||
fatal!("seconds specified multiple times")
|
||||
}
|
||||
Component::Seconds(_) => seconds = pending_num.take(),
|
||||
}
|
||||
}
|
||||
if pending_num.is_some() {
|
||||
fatal!("missing number unit after {}", pending_num.unwrap());
|
||||
}
|
||||
if minutes.is_none() && seconds.is_none() {
|
||||
fatal!("minutes and/or numbers must be specified");
|
||||
}
|
||||
interval = minutes.unwrap_or(0) * 60 + seconds.unwrap_or(0);
|
||||
}
|
||||
parse_duration(&args.interval).as_secs() as u64
|
||||
};
|
||||
tc.send(jay_idle::SetInterval {
|
||||
self_id: idle,
|
||||
interval,
|
||||
|
|
@ -140,47 +105,3 @@ impl Idle {
|
|||
tc.round_trip().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Component {
|
||||
Number(u64),
|
||||
Minutes(String),
|
||||
Seconds(String),
|
||||
}
|
||||
|
||||
fn parse_components(args: &[String]) -> Vec<Component> {
|
||||
let mut args = VecDeque::from_iter(args.iter().map(|s| s.to_ascii_lowercase()));
|
||||
let mut res = vec![];
|
||||
while let Some(arg) = args.pop_front() {
|
||||
if arg.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut arg = &arg[..];
|
||||
if is_num(arg.as_bytes()[0]) {
|
||||
if let Some(pos) = arg.as_bytes().iter().position(|&a| !is_num(a)) {
|
||||
args.push_front(arg[pos..].to_string());
|
||||
arg = &arg[..pos];
|
||||
}
|
||||
match u64::from_str(arg) {
|
||||
Ok(n) => res.push(Component::Number(n)),
|
||||
Err(e) => fatal!("Could not parse `{}` as a number: {}", arg, ErrorFmt(e)),
|
||||
}
|
||||
} else {
|
||||
if let Some(pos) = arg.as_bytes().iter().position(|&a| is_num(a)) {
|
||||
args.push_front(arg[pos..].to_string());
|
||||
arg = &arg[..pos];
|
||||
}
|
||||
let comp = match arg {
|
||||
"minutes" | "minute" | "min" | "m" => Component::Minutes(arg.to_string()),
|
||||
"seconds" | "second" | "sec" | "s" => Component::Seconds(arg.to_string()),
|
||||
_ => fatal!("Could not parse `{}`", arg),
|
||||
};
|
||||
res.push(comp);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn is_num(b: u8) -> bool {
|
||||
matches!(b, b'0'..=b'9')
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue