252 lines
7.8 KiB
Rust
252 lines
7.8 KiB
Rust
use iced::font;
|
|
use iced::time::{Duration, hours, minutes};
|
|
use iced::widget::{
|
|
center_x, center_y, column, container, row, scrollable, slider, table,
|
|
text, tooltip,
|
|
};
|
|
use iced::{Center, Element, Fill, Font, Right, Theme};
|
|
|
|
pub fn main() -> iced::Result {
|
|
iced::application(Table::new, Table::update, Table::view)
|
|
.theme(Theme::CatppuccinMocha)
|
|
.run()
|
|
}
|
|
|
|
struct Table {
|
|
events: Vec<Event>,
|
|
padding: (f32, f32),
|
|
separator: (f32, f32),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Message {
|
|
PaddingChanged(f32, f32),
|
|
SeparatorChanged(f32, f32),
|
|
}
|
|
|
|
impl Table {
|
|
fn new() -> Self {
|
|
Self {
|
|
events: Event::list(),
|
|
padding: (10.0, 5.0),
|
|
separator: (1.0, 1.0),
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, message: Message) {
|
|
match message {
|
|
Message::PaddingChanged(x, y) => self.padding = (x, y),
|
|
Message::SeparatorChanged(x, y) => self.separator = (x, y),
|
|
}
|
|
}
|
|
|
|
fn view(&self) -> Element<'_, Message> {
|
|
let table = {
|
|
let bold = |header| {
|
|
text(header).font(Font {
|
|
weight: font::Weight::Bold,
|
|
..Font::DEFAULT
|
|
})
|
|
};
|
|
|
|
let columns = [
|
|
table::column(bold("Name"), |event: &Event| text(&event.name)),
|
|
table::column(bold("Time"), |event: &Event| {
|
|
let minutes = event.duration.as_secs() / 60;
|
|
|
|
text!("{minutes} min").style(if minutes > 90 {
|
|
text::warning
|
|
} else {
|
|
text::default
|
|
})
|
|
})
|
|
.align_x(Right)
|
|
.align_y(Center),
|
|
table::column(bold("Price"), |event: &Event| {
|
|
if event.price > 0.0 {
|
|
text!("${:.2}", event.price).style(
|
|
if event.price > 100.0 {
|
|
text::warning
|
|
} else {
|
|
text::default
|
|
},
|
|
)
|
|
} else {
|
|
text("Free").style(text::success).width(Fill).center()
|
|
}
|
|
})
|
|
.align_x(Right)
|
|
.align_y(Center),
|
|
table::column(bold("Rating"), |event: &Event| {
|
|
text!("{:.2}", event.rating).style(if event.rating > 4.7 {
|
|
text::success
|
|
} else if event.rating < 2.0 {
|
|
text::danger
|
|
} else {
|
|
text::default
|
|
})
|
|
})
|
|
.align_x(Right)
|
|
.align_y(Center),
|
|
];
|
|
|
|
table(columns, &self.events)
|
|
.padding_x(self.padding.0)
|
|
.padding_y(self.padding.1)
|
|
.separator_x(self.separator.0)
|
|
.separator_y(self.separator.1)
|
|
};
|
|
|
|
let controls = {
|
|
let labeled_slider =
|
|
|label,
|
|
range: std::ops::RangeInclusive<f32>,
|
|
(x, y),
|
|
on_change: fn(f32, f32) -> Message| {
|
|
row![
|
|
text(label).font(Font::MONOSPACE).size(14).width(100),
|
|
tooltip(
|
|
slider(range.clone(), x, move |x| on_change(x, y)),
|
|
text!("{x:.0}px").font(Font::MONOSPACE).size(10),
|
|
tooltip::Position::Left
|
|
),
|
|
tooltip(
|
|
slider(range, y, move |y| on_change(x, y)),
|
|
text!("{y:.0}px").font(Font::MONOSPACE).size(10),
|
|
tooltip::Position::Right
|
|
),
|
|
]
|
|
.spacing(10)
|
|
.align_y(Center)
|
|
};
|
|
|
|
column![
|
|
labeled_slider(
|
|
"Padding",
|
|
0.0..=30.0,
|
|
self.padding,
|
|
Message::PaddingChanged
|
|
),
|
|
labeled_slider(
|
|
"Separator",
|
|
0.0..=5.0,
|
|
self.separator,
|
|
Message::SeparatorChanged
|
|
)
|
|
]
|
|
.spacing(10)
|
|
.width(400)
|
|
};
|
|
|
|
column![
|
|
center_y(scrollable(center_x(table)).spacing(10)).padding(10),
|
|
center_x(controls).padding(10).style(container::dark)
|
|
]
|
|
.into()
|
|
}
|
|
}
|
|
|
|
struct Event {
|
|
name: String,
|
|
duration: Duration,
|
|
price: f32,
|
|
rating: f32,
|
|
}
|
|
|
|
impl Event {
|
|
fn list() -> Vec<Self> {
|
|
vec![
|
|
Event {
|
|
name: "Get lost in a hacker bookstore".to_owned(),
|
|
duration: hours(2),
|
|
price: 0.0,
|
|
rating: 4.9,
|
|
},
|
|
Event {
|
|
name: "Buy vintage synth at Noisebridge flea market".to_owned(),
|
|
duration: hours(1),
|
|
price: 150.0,
|
|
rating: 4.8,
|
|
},
|
|
Event {
|
|
name: "Eat a questionable hot dog at 2AM".to_owned(),
|
|
duration: minutes(20),
|
|
price: 5.0,
|
|
rating: 1.7,
|
|
},
|
|
Event {
|
|
name: "Ride the MUNI for the story".to_owned(),
|
|
duration: minutes(60),
|
|
price: 3.0,
|
|
rating: 4.1,
|
|
},
|
|
Event {
|
|
name: "Scream into the void from Twin Peaks".to_owned(),
|
|
duration: minutes(40),
|
|
price: 0.0,
|
|
rating: 4.9,
|
|
},
|
|
Event {
|
|
name: "Buy overpriced coffee and feel things".to_owned(),
|
|
duration: minutes(25),
|
|
price: 6.5,
|
|
rating: 4.5,
|
|
},
|
|
Event {
|
|
name: "Attend an underground robot poetry slam".to_owned(),
|
|
duration: hours(1),
|
|
price: 12.0,
|
|
rating: 4.8,
|
|
},
|
|
Event {
|
|
name: "Browse cursed tech at a retro computer fair".to_owned(),
|
|
duration: hours(2),
|
|
price: 10.0,
|
|
rating: 4.7,
|
|
},
|
|
Event {
|
|
name: "Try to order at a secret ramen place with no sign"
|
|
.to_owned(),
|
|
duration: minutes(50),
|
|
price: 14.0,
|
|
rating: 4.6,
|
|
},
|
|
Event {
|
|
name: "Join a spontaneous rooftop drone rave".to_owned(),
|
|
duration: hours(3),
|
|
price: 0.0,
|
|
rating: 4.9,
|
|
},
|
|
Event {
|
|
name: "Sketch a stranger at Dolores Park".to_owned(),
|
|
duration: minutes(45),
|
|
price: 0.0,
|
|
rating: 4.4,
|
|
},
|
|
Event {
|
|
name: "Visit the Museum of Obsolete APIs".to_owned(),
|
|
duration: hours(1),
|
|
price: 9.99,
|
|
rating: 4.2,
|
|
},
|
|
Event {
|
|
name: "Chase the last working payphone".to_owned(),
|
|
duration: minutes(35),
|
|
price: 0.25,
|
|
rating: 4.0,
|
|
},
|
|
Event {
|
|
name: "Trade zines with a punk on BART".to_owned(),
|
|
duration: minutes(30),
|
|
price: 3.5,
|
|
rating: 4.7,
|
|
},
|
|
Event {
|
|
name: "Get a tattoo of the Git logo".to_owned(),
|
|
duration: hours(1),
|
|
price: 200.0,
|
|
rating: 4.6,
|
|
},
|
|
]
|
|
}
|
|
}
|