use crate::panels::RenderPanel;
use crate::splits::Split;
use anyhow::Result;
use mist_core::{
config::{Colors, Config, Panel},
error::MistError::Str,
timer::{
dump::StateDump,
format,
state::{calc_status, RunUpdate, SplitStatus, StateChange},
Comparison, Run,
},
};
#[cfg(feature = "bg")]
use sdl2::{
gfx::rotozoom::RotozoomSurface, image::LoadSurface, pixels::PixelFormatEnum, surface::Surface,
};
use sdl2::{
pixels::Color,
rect::{Point, Rect},
render::{BlendMode, Texture, TextureCreator, TextureQuery, WindowCanvas},
ttf::Font,
video::WindowContext,
};
use std::{cell::RefCell, cmp::max, convert::TryInto, rc::Rc};
const ALL_CHARS: &str =
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz`1234567890[]~!@#$%^&*(){}',./=\\-;\"<>?+|_:";
const TIMER_CHARS: &str = "1234567890:.-";
pub struct RenderState<'a, 'b> {
run: Rc<RefCell<Run>>,
canvas: WindowCanvas,
creator: TextureCreator<WindowContext>,
colors: Colors,
splits: Vec<Split>,
panels: Vec<RenderPanel>,
map: FontMap,
time_str: String,
time_rounding: Option<u128>,
is_running: bool,
rebuild: bool,
timer_font: Font<'b, 'a>,
timer_height: u32,
splits_font: Font<'b, 'a>,
splits_height: u32,
ms_ratio: f32,
top_index: usize,
bottom_index: usize,
highlighted: usize,
current: usize,
max_splits: usize,
inline: bool,
status: SplitStatus,
comparison: Comparison,
#[cfg(feature = "bg")]
background: Background,
}
#[cfg(feature = "bg")]
enum Background {
NoBackground,
HasBackground { tex: Texture, rect: Rect },
}
struct FontMap {
tex: Texture,
coords: Vec<u32>,
}
impl<'a, 'b> RenderState<'a, 'b> {
pub fn new(
run: Rc<RefCell<Run>>,
mut canvas: WindowCanvas,
config: &Config,
timer_font: Font<'b, 'a>,
splits_font: Font<'b, 'a>,
) -> Result<Self> {
canvas.clear();
let creator = canvas.texture_creator();
let panels = {
let mut ret = vec![];
let mut set_sob = 0;
for panel in config.panels() {
let (text, time) = match panel {
&Panel::Pace { golds } => {
let text = if golds { "Pace (best)" } else { "Pace (pb)" };
(text.into(), "- ".into())
}
Panel::SumOfBest => {
let sob = run
.borrow()
.gold_times()
.iter()
.map(|t| t.val())
.sum::<u128>();
set_sob = sob;
let time = format::split_time_text(sob);
("Sum of Best".into(), time)
}
&Panel::CurrentSplitDiff { golds } => {
let text = if golds { "Split (best)" } else { "Split (pb)" };
(text.into(), "- ".into())
}
Panel::Custom { title, value } => (title.to_string(), value.to_string()),
};
let time_tex = render_text(&time, &splits_font, &creator, config.colors().text)?;
let text_tex = render_text(&text, &splits_font, &creator, config.colors().text)?;
let mut newpanel = RenderPanel::new(text_tex, time_tex, panel.clone());
newpanel.set_timeval(set_sob);
ret.push(newpanel);
}
ret
};
let times = run.borrow().pb_times().clone();
let string_times: Vec<String> = format::split_time_sum(&run.borrow().pb_times_u128())
.iter()
.enumerate()
.map(|(idx, &t)| {
if t == 0 || !times[idx].is_time() {
"- ".into()
} else {
format::split_time_text(t)
}
})
.collect();
let splits: Vec<Split> = run
.borrow()
.splits()
.iter()
.enumerate()
.map(|(idx, name)| {
Split::new(
render_text(name, &splits_font, &creator, config.colors().text).unwrap(),
render_text(
&string_times[idx],
&splits_font,
&creator,
config.colors().text,
)
.unwrap(),
None,
None,
)
})
.collect();
let splits_height = splits_font.size_of(ALL_CHARS)?.1;
let timer_height = timer_font.size_of(TIMER_CHARS)?.1;
let panels_height: u32 = panels.iter().map(|p| p.height()).sum();
let bottom_index: usize;
let max_splits: usize;
let max_initial_splits = ((canvas.viewport().height() - timer_height - panels_height)
/ ((splits_height * (1 + !config.inline_splits() as u32)) + 5))
as usize;
if splits.is_empty() {
max_splits = 0;
bottom_index = 0;
} else if max_initial_splits > splits.len() {
bottom_index = splits.len() - 1;
max_splits = splits.len();
} else {
max_splits = max_initial_splits;
bottom_index = max_initial_splits - 1;
}
let time_str = if run.borrow().offset().is_time() {
format!(
"-{}",
format::ms_to_readable(run.borrow().offset().val(), None)
)
} else {
"0.000".into()
};
canvas.window_mut().set_minimum_size(
100,
timer_height + 20 + (splits_height * panels.len() as u32),
)?;
canvas.window_mut().set_title(&get_title(&run.borrow()))?;
canvas.set_blend_mode(BlendMode::Blend);
canvas.present();
Ok(Self {
run,
colors: config.colors(),
splits,
panels,
map: FontMap::generate(&timer_font, &creator, config.colors().text)?,
time_str,
time_rounding: config.rounding(),
is_running: false,
rebuild: false,
timer_font,
timer_height,
splits_font,
splits_height,
ms_ratio: config.ms_ratio(),
top_index: 0,
bottom_index,
highlighted: usize::MAX,
current: 0,
max_splits,
inline: config.inline_splits(),
status: SplitStatus::None,
comparison: Comparison::PersonalBest,
#[cfg(feature = "bg")]
background: Background::load(config, canvas.viewport(), &creator)?,
canvas,
creator,
})
}
pub fn update(&mut self, update: &RunUpdate) -> Result<()> {
if update.status != self.status {
self.status = update.status;
let color = self.convert_color(self.status);
self.map = FontMap::generate(&self.timer_font, &self.creator, color).unwrap();
}
if self.status != SplitStatus::None {
for panel in &mut self.panels {
match *panel.panel_type() {
Panel::Pace { golds }
if self.run.borrow().pb_times()[self.current].raw() != 0 =>
{
let r = self.run.borrow();
let times = if golds { r.gold_times() } else { r.pb_times() };
let prev_time = update.time - update.split_time;
let cur_split_pace = max(times[self.current].val(), update.split_time);
let pace = format::split_time_text(
times[self.current + 1..]
.iter()
.map(|t| t.val())
.sum::<u128>()
+ prev_time
+ cur_split_pace,
);
panel.set_time(render_text(
pace,
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
Panel::CurrentSplitDiff { golds }
if self.splits.len() > 1
&& self.run.borrow().pb_times()[self.current].is_time() =>
{
let compare_time: u128 = if golds {
self.run.borrow().gold_times()[self.current].raw()
} else {
self.run.borrow().pb_times()[self.current].raw()
};
let time = if !golds {
if update.split_time < compare_time {
format::diff_text(-((compare_time - update.split_time) as i128))
} else {
format::diff_text((update.split_time - compare_time) as i128)
}
} else if update.split_time < compare_time {
format::diff_text(-((compare_time - update.split_time) as i128))
} else {
format::diff_text((update.split_time - compare_time) as i128)
};
panel.set_time(render_text(
time,
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
_ => {}
}
}
}
if self.rebuild {
self.rebuild = false;
self.rebuild_comparison()?;
}
for change in &update.change {
match change {
StateChange::Pause => {
self.is_running = false;
self.highlighted = usize::MAX;
self.time_str = format::ms_to_readable(update.time, self.time_rounding);
}
StateChange::Finish { .. } => {
self.is_running = false;
self.time_str = format::ms_to_readable(update.time, self.time_rounding);
self.highlighted = usize::MAX;
self.rebuild = true;
}
StateChange::Unpause { .. } => {
self.is_running = true;
}
&StateChange::ExitSplit {
status, time, diff, ..
} => {
if !self.run.borrow().splits().is_empty() {
if status == SplitStatus::Gold {
for panel in &mut self.panels {
if *panel.panel_type() == Panel::SumOfBest {
panel.set_timeval(
panel.timeval()
- self.run.borrow().gold_times()[self.current].val()
+ time,
);
panel.set_time(render_text(
format::split_time_text(panel.timeval()),
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
}
}
if time == 0 {
self.splits[self.current].set_cur(Some(render_text(
"- ",
&self.splits_font,
&self.creator,
self.colors.text,
)?));
} else {
let mut color = self.convert_color(status);
let time_str = if (self.comparison == Comparison::PersonalBest
&& self.run.borrow().pb_times()[self.current].is_time())
|| (self.comparison == Comparison::Golds
&& self.run.borrow().gold_times()[self.current].is_time())
|| (self.comparison == Comparison::Average
&& self.run.borrow().sum_times()[self.current].1.is_time())
{
format::diff_text(diff)
} else {
color = self.colors.text;
"- ".into()
};
self.splits[self.current].set_diff(Some(render_text(
&time_str,
&self.splits_font,
&self.creator,
color,
)?));
let time_str = format::split_time_text(update.time);
self.splits[self.current].set_cur(Some(render_text(
&time_str,
&self.splits_font,
&self.creator,
self.colors.text,
)?));
}
}
}
&StateChange::EnterSplit { idx } => {
self.is_running = true;
if idx < self.current {
self.splits[idx].set_cur(None);
self.splits[idx].set_diff(None);
}
self.current = idx;
if self.current > self.bottom_index {
self.top_index += self.current - self.bottom_index;
self.bottom_index = self.current;
} else if self.current < self.top_index {
self.bottom_index -= self.top_index - self.current;
self.top_index = self.current;
}
self.update_highlighted();
}
StateChange::Reset { .. } => {
self.current = 0;
self.top_index = 0;
self.highlighted = usize::MAX;
if self.max_splits == 0 {
self.bottom_index = 0;
} else {
self.bottom_index = self.max_splits - 1;
}
if self.run.borrow().offset().is_time() {
self.time_str = format!(
"-{}",
format::ms_to_readable(self.run.borrow().offset().val(), None)
);
} else {
self.time_str = "0.000".into();
}
for split in &mut self.splits {
split.set_cur(None);
split.set_diff(None);
}
for panel in &mut self.panels {
if !matches!(panel.panel_type(), Panel::SumOfBest) {
panel.set_time(render_text(
"- ",
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
}
self.is_running = false;
}
&StateChange::ComparisonChanged { comp } => {
self.comparison = comp;
self.rebuild = true;
}
StateChange::EnterOffset => {
self.is_running = true;
}
_ => {}
}
}
if self.is_running {
if update.offset {
self.time_str = format!(
"-{}",
format::ms_to_readable(self.run.borrow().offset().val() - update.time, None)
);
} else {
self.time_str = format::ms_to_readable(update.time, None);
}
}
self.update_highlighted();
Ok(())
}
pub fn scroll(&mut self, y: i32) {
if y == -1 && !self.splits.is_empty() && self.bottom_index < self.splits.len() - 1 {
self.bottom_index += 1;
self.top_index += 1;
} else if y == 1 && self.top_index != 0 {
self.bottom_index -= 1;
self.top_index -= 1;
}
self.update_highlighted();
}
pub fn win_resize(&mut self, y: u32) {
let row_height = self.splits_height + 5 + (!self.inline as u32 * self.splits_height);
let all_rows_height = row_height * self.max_splits as u32;
let bottom_height = self.timer_height + (self.splits_height * self.panels.len() as u32);
if y - bottom_height > all_rows_height + row_height {
let diff = (((y - bottom_height) - all_rows_height) / row_height) as usize;
if self.max_splits + diff < self.splits.len() {
self.max_splits += diff;
} else {
self.max_splits = self.splits.len();
}
if self.top_index > diff {
self.top_index -= diff;
} else if self.top_index != 0 {
let bottom_change = diff - self.top_index;
self.top_index = 0;
if self.bottom_index + bottom_change < self.splits.len() - 1 {
self.bottom_index += bottom_change;
} else {
self.bottom_index = self.splits.len() - 1;
}
} else if !self.splits.is_empty() && self.bottom_index + diff < self.splits.len() - 1 {
self.bottom_index += diff;
} else if !self.splits.is_empty() {
self.bottom_index = self.splits.len() - 1;
} else {
self.bottom_index = 0;
}
} else if y - bottom_height < all_rows_height {
let diff = ((all_rows_height - (y - bottom_height)) / row_height) as usize + 1;
if self.max_splits > diff {
self.max_splits -= diff;
} else {
self.max_splits = 0;
self.top_index = 0;
self.bottom_index = 0;
self.update_highlighted();
return;
}
if self.bottom_index - diff > self.top_index {
self.bottom_index -= diff;
} else {
self.bottom_index = self.top_index;
}
}
self.update_highlighted();
}
pub fn render(&mut self) -> Result<()> {
self.canvas.set_draw_color(self.colors.background);
self.canvas.clear();
#[cfg(feature = "bg")]
self.render_bg()?;
self.render_panels()?;
self.render_rows()?;
self.render_time()?;
self.canvas.present();
Ok(())
}
pub fn reload_run(&mut self) -> Result<()> {
let string_times: Vec<String> = format::split_time_sum(&self.run.borrow().pb_times_u128())
.iter()
.map(|&t| {
if t == 0 {
"- ".into()
} else {
format::split_time_text(t)
}
})
.collect();
self.splits = vec![];
for (idx, name) in self.run.borrow().splits().iter().enumerate() {
self.splits.push(Split::new(
render_text(name, &self.splits_font, &self.creator, self.colors.text)?,
render_text(
&string_times[idx],
&self.splits_font,
&self.creator,
self.colors.text,
)?,
None,
None,
));
}
self.canvas
.window_mut()
.set_title(&get_title(&self.run.borrow()))?;
if self.run.borrow().offset().is_time() {
self.time_str = format!(
"-{}",
format::ms_to_readable(self.run.borrow().offset().val(), None)
);
} else {
self.time_str = "0.000".into();
}
for panel in &mut self.panels {
match panel.panel_type() {
Panel::Pace { .. } | Panel::CurrentSplitDiff { .. } => {
panel.set_time(render_text(
"- ",
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
Panel::SumOfBest => {
let sob = self
.run
.borrow()
.gold_times()
.iter()
.map(|t| t.val())
.sum::<u128>();
panel.set_timeval(sob);
panel.set_time(render_text(
format::split_time_text(sob),
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
_ => {}
}
}
self.top_index = 0;
self.highlighted = usize::MAX;
self.current = 0;
self.status = SplitStatus::None;
let max_initial_splits = ((self.canvas.viewport().height() - self.timer_height)
/ ((self.splits_height * (1 + !self.inline as u32)) + 5))
as usize;
if self.splits.is_empty() {
self.max_splits = 0;
self.bottom_index = 0;
} else if max_initial_splits > self.splits.len() {
self.max_splits = self.splits.len();
self.bottom_index = self.splits.len() - 1;
} else {
self.max_splits = max_initial_splits;
self.bottom_index = max_initial_splits - 1;
}
Ok(())
}
pub fn reload_config(self, config: &Config) -> Result<Self> {
Self::new(
self.run,
self.canvas,
config,
self.timer_font,
self.splits_font,
)
}
pub fn win_size(&self) -> (u32, u32) {
self.canvas.window().size()
}
fn update_highlighted(&mut self) {
if self.is_running && self.current >= self.top_index && self.current <= self.bottom_index {
self.highlighted = self.current - self.top_index;
} else {
self.highlighted = usize::MAX;
}
}
fn rebuild_comparison(&mut self) -> Result<()> {
match self.comparison {
Comparison::None => {
for split in &mut self.splits {
split.set_comp(render_text(
"- ",
&self.splits_font,
&self.creator,
self.colors.text,
)?);
}
}
Comparison::Average => {
let mut i = 0;
let (attempts, times) = {
let mut att = vec![];
let mut tm = vec![];
for sum in self.run.borrow().sum_times() {
att.push(sum.0);
tm.push(sum.1);
}
(att, tm)
};
let mut avgs = vec![];
while i < attempts.len() {
avgs.push(if attempts[i] != 0 {
times[i] / attempts[i]
} else {
attempts[i]
});
i += 1;
}
let split_times_raw: Vec<String> = format::split_time_sum(&avgs)
.iter()
.enumerate()
.map(|(idx, &t)| {
if t == 0 || !times[idx].is_time() {
"- ".into()
} else {
format::split_time_text(t)
}
})
.collect();
i = 0;
while i < self.splits.len() {
self.splits[i].set_comp(render_text(
&split_times_raw[i],
&self.splits_font,
&self.creator,
self.colors.text,
)?);
i += 1;
}
}
c => {
let split_times = match c {
Comparison::PersonalBest => self.run.borrow().pb_times_u128(),
Comparison::Golds => self.run.borrow().gold_times_u128(),
_ => unreachable!(),
};
let times = self.run.borrow().pb_times().clone();
let split_times_raw: Vec<String> = format::split_time_sum(&split_times)
.iter()
.enumerate()
.map(|(idx, &t)| {
if t == 0 || !times[idx].is_time() {
"- ".into()
} else {
format::split_time_text(t)
}
})
.collect();
let mut i = 0;
while i < self.splits.len() {
self.splits[i].set_comp(render_text(
&split_times_raw[i],
&self.splits_font,
&self.creator,
self.colors.text,
)?);
i += 1;
}
}
}
Ok(())
}
fn render_rows(&mut self) -> Result<()> {
let on_screen = if self.max_splits > 0 {
&self.splits[self.top_index..=self.bottom_index]
} else {
&[]
};
let incr_height: i32 = (self.splits_height * (!self.inline as u32 + 1)) as i32;
let mut y = 0;
let mut row: Rect;
let window_width = self.canvas.viewport().width();
for (index, item) in on_screen.iter().enumerate() {
let TextureQuery { width, height, .. } = item.name().query();
if index == self.highlighted {
self.canvas.set_draw_color(self.colors.highlight);
self.canvas
.fill_rect(Rect::new(0, y - 1, window_width, incr_height as u32 + 5))
.map_err(Str)?;
}
row = Rect::new(0, y, width, height);
self.canvas
.copy(item.name(), None, Some(row))
.map_err(Str)?;
let num_y = if self.inline {
y
} else {
y + self.splits_height as i32
};
let TextureQuery {
width: tinfo_width, ..
} = match item.cur() {
Some(x) => {
let tinfo = x.query();
row = Rect::new(
(window_width - tinfo.width) as i32,
num_y,
tinfo.width,
tinfo.height,
);
self.canvas.copy(x, None, Some(row)).map_err(Str)?;
tinfo
}
None => {
let tinfo = item.comp().query();
row = Rect::new(
(window_width - tinfo.width) as i32,
num_y,
tinfo.width,
tinfo.height,
);
self.canvas
.copy(item.comp(), None, Some(row))
.map_err(Str)?;
tinfo
}
};
if let Some(x) = item.diff() {
let TextureQuery {
width: dw,
height: dh,
..
} = x.query();
row = Rect::new(
((window_width - tinfo_width - 25) - dw) as i32,
num_y,
dw,
dh,
);
self.canvas.copy(x, None, Some(row)).map_err(Str)?;
}
self.canvas.set_draw_color(self.colors.line);
y += incr_height + 3;
self.canvas
.draw_line(Point::new(0, y), Point::new(window_width as i32, y))
.map_err(Str)?;
y += 2;
}
Ok(())
}
fn render_time(&mut self) -> Result<()> {
let coords = self.map.gen_str_coords(&self.time_str, self.ms_ratio);
let vp = self.canvas.viewport();
let h = vp.height();
let w = vp.width();
let mut src = Rect::new(0, 0, 0, self.timer_height);
let starting_y = (self.timer_height as f32 * self.ms_ratio) as u32;
let diff =
(self.timer_height - self.timer_font.find_glyph_metrics('0').unwrap().maxy as u32) / 2;
let mut dst = Rect::new(
0,
(h - (starting_y + (diff as f32 * (1.0 - self.ms_ratio)) as u32)
- (self.splits_height * self.panels.len() as u32)) as i32,
0,
starting_y,
);
for (idx, &(sx, sw, dx, dw)) in coords.iter().enumerate() {
src.set_x(sx.try_into().unwrap());
src.set_width(sw);
let wdx: i32 = if w < dx {
let tmp: i32 = (dx - w).try_into().unwrap();
-tmp
} else {
(w - dx).try_into().unwrap()
};
dst.set_x(wdx);
dst.set_width(dw);
if idx == 3 {
dst.set_y(
(h - self.timer_height - (self.splits_height * self.panels.len() as u32))
as i32,
);
dst.set_height(self.timer_height);
}
self.canvas
.copy(&self.map.tex, Some(src), Some(dst))
.map_err(Str)?;
}
Ok(())
}
fn render_panels(&mut self) -> Result<()> {
let mut num = 1;
for panel in &self.panels {
let TextureQuery { width, height, .. } = panel.text().query();
self.canvas
.copy(
panel.text(),
None,
Some(Rect::new(
0,
(self.canvas.viewport().height() - (num * height)) as i32,
width,
height,
)),
)
.map_err(Str)?;
let TextureQuery { width, height, .. } = panel.time().query();
self.canvas
.copy(
panel.time(),
None,
Some(Rect::new(
self.canvas.viewport().width() as i32 - width as i32,
(self.canvas.viewport().height() - (num * height)) as i32,
width,
height,
)),
)
.map_err(Str)?;
num += 1;
}
Ok(())
}
#[cfg(feature = "bg")]
fn render_bg(&mut self) -> Result<()> {
if let Background::HasBackground { ref tex, rect } = self.background {
self.canvas.copy(tex, None, Some(rect)).map_err(Str)?;
}
Ok(())
}
pub fn read_dump(&mut self, dump: &StateDump) -> Result<()> {
self.reload_run()?;
self.time_str = format::ms_to_readable(dump.time, self.time_rounding);
self.status = dump.status;
self.comparison = dump.comparison;
self.current = dump.current_split;
if self.current > self.bottom_index {
self.top_index += self.current - self.bottom_index;
self.bottom_index = self.current;
} else if self.current < self.top_index {
self.bottom_index -= self.top_index - self.current;
self.top_index = self.current;
}
self.update_highlighted();
self.rebuild_current(dump)?;
Ok(())
}
fn rebuild_current(&mut self, dump: &StateDump) -> Result<()> {
let raw_diffs = dump.run_diffs.iter().map(|v| v.raw()).collect::<Vec<_>>();
let diff_sums = format::split_time_sum(&raw_diffs);
let stats = calculate_statuses(&raw_diffs, &dump.run_golds);
let run_times = format::split_time_sum(
&dump
.run_times
.iter()
.filter_map(|t| if t.is_none() { None } else { Some(t.raw()) })
.collect::<Vec<_>>(),
);
let run_pb_times = dump.run.pb_times();
for (i, &time) in run_times.iter().enumerate() {
let time_str = if dump.run_times[i].is_time() {
format::split_time_text(time)
} else {
"- ".into()
};
self.splits[i].set_cur(Some(render_text(
&time_str,
&self.splits_font,
&self.creator,
self.colors.text,
)?));
if dump.run_times[i].is_time() {
let time_str = match self.comparison {
Comparison::Average if dump.run.sum_times()[i].1.is_time() => {
format::diff_text(diff_sums[i])
}
Comparison::PersonalBest if run_pb_times[i].is_time() => {
format::diff_text(diff_sums[i])
}
Comparison::Golds if dump.run.gold_times()[i].is_time() => {
format::diff_text(diff_sums[i])
}
_ => "- ".into(),
};
let color = self.convert_color(stats[i]);
self.splits[i].set_diff(Some(render_text(
&time_str,
&self.splits_font,
&self.creator,
color,
)?));
}
}
Ok(())
}
fn convert_color(&self, status: SplitStatus) -> (u8, u8, u8, u8) {
use SplitStatus::*;
match status {
Ahead => self.colors.ahead,
Behind => self.colors.behind,
Losing => self.colors.losing,
Gaining => self.colors.gaining,
Gold => self.colors.gold,
None => self.colors.text,
}
}
}
fn calculate_statuses(diffs: &[i128], golds: &[bool]) -> Vec<SplitStatus> {
let mut sum = 0;
let mut ret = vec![];
for (diff, &gold) in diffs.iter().zip(golds) {
let last_sum = sum;
sum += diff;
if gold {
ret.push(SplitStatus::Gold);
} else {
ret.push(calc_status(sum, last_sum));
}
}
ret
}
fn get_title(r: &Run) -> String {
let t = if r.game_title().is_empty() {
String::new()
} else {
format!(": {}", r.game_title())
};
let c = if r.category().is_empty() {
String::new()
} else if t.is_empty() {
format!(": {}", r.category())
} else {
format!(" ({})", r.category())
};
format!("mist{}{}", t, c)
}
impl FontMap {
fn generate<C: Into<Color>>(
font: &Font<'_, '_>,
creator: &TextureCreator<WindowContext>,
color: C,
) -> Result<Self> {
let mut max = 0;
let mut sum = 0;
let mut coords = vec![0];
for chr in "-0123456789:. ".chars() {
let temp = font.size_of(&chr.to_string())?.0;
sum += temp;
if temp > max {
max = temp;
}
coords.push(sum);
}
coords.push(max);
let surface = font.render("- 0 1 2 3 4 5 6 7 8 9 : .").blended(color)?;
Ok(Self {
tex: creator.create_texture_from_surface(&surface)?,
coords,
})
}
fn gen_str_coords(&self, string: &str, ms_ratio: f32) -> Vec<(u32, u32, u32, u32)> {
let mut coord_idx;
let mut ret: Vec<(u32, u32, u32, u32)> = vec![];
let mut x = 0;
let space = self.coords[14] - self.coords[13];
for (idx, chr) in string.chars().rev().enumerate() {
coord_idx = match chr {
'-' => 0,
'0' => 1,
'1' => 2,
'2' => 3,
'3' => 4,
'4' => 5,
'5' => 6,
'6' => 7,
'7' => 8,
'8' => 9,
'9' => 10,
':' => 11,
'.' => 12,
_ => 0,
};
let width = self.coords[coord_idx + 1] - self.coords[coord_idx];
x += if chr == ':' || chr == '.' {
width
} else if idx < 3 {
(self.coords[15] as f32 * ms_ratio) as u32
} else {
self.coords[15]
};
let tup = (
self.coords[coord_idx] + (coord_idx as u32 * space),
width,
x,
if idx < 3 {
(width as f32 * ms_ratio) as u32
} else {
width
},
);
ret.push(tup);
}
ret
}
}
#[cfg(feature = "bg")]
impl Background {
fn load(
config: &Config,
viewport: Rect,
creator: &TextureCreator<WindowContext>,
) -> Result<Self> {
let bg: Option<Surface> = match config.img() {
Some(p) => Some(Surface::from_file(p).map_err(Str)?),
None => None,
};
if let Some(x) = bg {
let bg_tex: Texture;
let width = viewport.width();
let height = viewport.height();
if !config.img_scaled() {
let mut sur = Surface::new(width, height, PixelFormatEnum::RGBA32).map_err(Str)?;
let cutoffx = {
if x.width() > width {
((x.width() - width) / 2) as i32
} else {
0
}
};
let cutoffy = {
if x.height() > height {
((x.height() - height) / 2) as i32
} else {
0
}
};
x.blit(Rect::new(cutoffx, cutoffy, width, height), &mut sur, None)
.map_err(Str)?;
bg_tex = creator.create_texture_from_surface(&sur)?;
} else {
let sur: Surface;
let width = width as f64;
let height = height as f64;
let xw = x.width() as f64;
let xh = x.height() as f64;
if (xw / width) > (xh / height) {
if width < xw {
sur = x.zoom(width / xw, width / xw, true).map_err(Str)?;
} else {
sur = x.zoom(xw / width, xw / width, true).map_err(Str)?;
}
} else {
if height < xh {
sur = x.zoom(height / xh, height / xh, true).map_err(Str)?;
} else {
sur = x.zoom(xh / height, xh / height, true).map_err(Str)?;
}
}
bg_tex = creator.create_texture_from_surface(&sur)?;
}
let sdl2::render::TextureQuery {
width: bgw,
height: bgh,
..
} = bg_tex.query();
Ok(Background::HasBackground {
tex: bg_tex,
rect: Rect::new(0, 0, bgw, bgh),
})
} else {
Ok(Background::NoBackground)
}
}
}
fn render_text<T: ToString, C: Into<Color>>(
text: T,
font: &sdl2::ttf::Font,
creator: &sdl2::render::TextureCreator<sdl2::video::WindowContext>,
color: C,
) -> Result<Texture> {
let sur = font.render(&text.to_string()).blended(color)?;
Ok(creator.create_texture_from_surface(sur)?)
}