use crate::resources::SnapshotId;
use crate::session::{Session, SessionCoords};
use crate::util;
use rgx::core::{Rect, Rgba8};
use rgx::kit::Animation;
use rgx::math::*;
use std::collections::btree_map;
use std::collections::{BTreeMap, VecDeque};
use std::fmt;
use std::ops::Deref;
use std::path::PathBuf;
use std::time;
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
pub struct ViewId(u16);
impl Default for ViewId {
fn default() -> Self {
ViewId(0)
}
}
impl fmt::Display for ViewId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Copy, Clone, PartialEq)]
pub struct ViewCoords<T>(Point2<T>);
impl<T> ViewCoords<T> {
pub fn new(x: T, y: T) -> Self {
Self(Point2::new(x, y))
}
}
impl ViewCoords<i32> {
pub fn clamp(&mut self, rect: Rect<i32>) {
util::clamp(&mut self.0, rect);
}
}
impl<T> Deref for ViewCoords<T> {
type Target = Point2<T>;
fn deref(&self) -> &Point2<T> {
&self.0
}
}
impl Into<ViewCoords<i32>> for ViewCoords<f32> {
fn into(self) -> ViewCoords<i32> {
ViewCoords::new(self.x.round() as i32, self.y.round() as i32)
}
}
impl Into<ViewCoords<f32>> for ViewCoords<i32> {
fn into(self) -> ViewCoords<f32> {
ViewCoords::new(self.x as f32, self.y as f32)
}
}
impl Into<ViewCoords<u32>> for ViewCoords<f32> {
fn into(self) -> ViewCoords<u32> {
ViewCoords::new(self.x.round() as u32, self.y.round() as u32)
}
}
impl From<Point2<f32>> for ViewCoords<f32> {
fn from(p: Point2<f32>) -> ViewCoords<f32> {
ViewCoords::new(p.x, p.y)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ViewExtent {
pub fw: u32,
pub fh: u32,
pub nframes: usize,
}
impl ViewExtent {
pub fn new(fw: u32, fh: u32, nframes: usize) -> Self {
ViewExtent { fw, fh, nframes }
}
pub fn width(&self) -> u32 {
self.fw * self.nframes as u32
}
pub fn height(&self) -> u32 {
self.fh
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewState {
Okay,
Dirty,
Damaged,
}
#[derive(Debug, Clone)]
pub enum ViewOp {
Blit(Rect<f32>, Rect<f32>),
Clear(Rgba8),
Yank(Rect<i32>),
Paste(Rect<i32>),
}
#[derive(Debug)]
pub struct View {
pub fw: u32,
pub fh: u32,
pub offset: Vector2<f32>,
pub id: ViewId,
pub zoom: f32,
pub ops: Vec<ViewOp>,
pub flip_x: bool,
pub flip_y: bool,
pub file_status: FileStatus,
pub state: ViewState,
pub animation: Animation<Rect<f32>>,
saved_snapshot: Option<SnapshotId>,
}
impl View {
const DEFAULT_ANIMATION_DELAY: u64 = 160;
pub fn new(id: ViewId, fs: FileStatus, fw: u32, fh: u32) -> Self {
let saved_snapshot = if let FileStatus::Saved(_) = &fs {
Some(SnapshotId::default())
} else {
None
};
Self {
id,
fw,
fh,
offset: Vector2::zero(),
zoom: 1.,
ops: Vec::new(),
flip_x: false,
flip_y: false,
file_status: fs,
animation: Animation::new(
&[Rect::origin(fw as f32, fh as f32)],
time::Duration::from_millis(Self::DEFAULT_ANIMATION_DELAY),
),
state: ViewState::Okay,
saved_snapshot,
}
}
pub fn width(&self) -> u32 {
self.fw * self.animation.len() as u32
}
pub fn height(&self) -> u32 {
self.fh
}
pub fn size(&self) -> (u32, u32) {
(self.width(), self.height())
}
pub fn file_name(&self) -> Option<&PathBuf> {
match self.file_status {
FileStatus::New(ref f) => Some(f),
FileStatus::Modified(ref f) => Some(f),
FileStatus::Saved(ref f) => Some(f),
FileStatus::NoFile => None,
}
}
pub fn save_as(&mut self, id: SnapshotId, path: PathBuf) {
match self.file_status {
FileStatus::Modified(ref curr_path) | FileStatus::New(ref curr_path) => {
if curr_path == &path {
self.saved(id, path);
}
}
FileStatus::NoFile => {
self.saved(id, path);
}
FileStatus::Saved(_) => {}
}
}
pub fn extend(&mut self) {
let w = self.width() as f32;
let fw = self.fw as f32;
let fh = self.fh as f32;
self.animation.push_frame(Rect::new(w, 0., w + fw, fh));
self.touch();
}
pub fn shrink(&mut self) {
if self.animation.len() > 1 {
self.animation.pop_frame();
self.touch();
}
}
pub fn extend_clone(&mut self, index: i32) {
let width = self.width() as f32;
let (fw, fh) = (self.fw as f32, self.fh as f32);
let index = if index == -1 {
self.animation.len() - 1
} else {
index as usize
};
self.ops.push(ViewOp::Blit(
Rect::new(fw * index as f32, 0., fw * (index + 1) as f32, fh),
Rect::new(width, 0., width + fw, fh),
));
self.extend();
}
pub fn resize_frames(&mut self, fw: u32, fh: u32) {
self.reset(ViewExtent::new(fw, fh, self.animation.len()));
}
pub fn clear(&mut self, color: Rgba8) {
self.ops.push(ViewOp::Clear(color));
self.touch();
}
pub fn yank(&mut self, area: Rect<i32>) {
self.ops.push(ViewOp::Yank(area));
}
pub fn paste(&mut self, area: Rect<i32>) {
self.ops.push(ViewOp::Paste(area));
self.touch();
}
pub fn reset(&mut self, extent: ViewExtent) {
self.fw = extent.fw;
self.fh = extent.fh;
let mut frames = Vec::new();
let origin = Rect::origin(self.fw as f32, self.fh as f32);
for i in 0..extent.nframes {
frames.push(origin + Vector2::new(i as f32 * self.fw as f32, 0.));
}
self.animation = Animation::new(&frames, self.animation.delay);
}
pub fn slice(&mut self, nframes: usize) -> bool {
if self.width() % nframes as u32 == 0 {
let fw = self.width() / nframes as u32;
self.reset(ViewExtent::new(fw, self.fh, nframes));
return true;
}
false
}
#[allow(dead_code)]
pub fn play_animation(&mut self) {
self.animation.play();
}
#[allow(dead_code)]
pub fn pause_animation(&mut self) {
self.animation.pause();
}
#[allow(dead_code)]
pub fn stop_animation(&mut self) {
self.animation.stop();
}
pub fn set_animation_delay(&mut self, ms: u64) {
self.animation.delay = time::Duration::from_millis(ms);
}
pub fn okay(&mut self) {
self.state = ViewState::Okay;
self.ops.clear();
}
pub fn update(&mut self, delta: time::Duration) {
self.animation.step(delta);
}
pub fn rect(&self) -> Rect<f32> {
Rect::new(
self.offset.x,
self.offset.y,
self.offset.x + self.width() as f32 * self.zoom,
self.offset.y + self.height() as f32 * self.zoom,
)
}
pub fn contains(&self, p: SessionCoords) -> bool {
self.rect().contains(*p)
}
pub fn center(&self) -> ViewCoords<f32> {
ViewCoords::new(self.width() as f32 / 2., self.height() as f32 / 2.)
}
pub fn touch(&mut self) {
if let FileStatus::Saved(ref f) = self.file_status {
self.file_status = FileStatus::Modified(f.clone());
}
self.state = ViewState::Dirty;
}
pub fn damaged(&mut self) {
self.state = ViewState::Damaged;
}
pub fn is_damaged(&self) -> bool {
self.state == ViewState::Damaged
}
pub fn is_dirty(&self) -> bool {
self.state == ViewState::Dirty
}
pub fn is_okay(&self) -> bool {
self.state == ViewState::Okay
}
pub fn status(&self) -> String {
self.file_status.to_string()
}
pub fn extent(&self) -> ViewExtent {
ViewExtent::new(self.fw, self.fh, self.animation.len())
}
pub fn bounds(&self) -> Rect<i32> {
Rect::origin(self.width() as i32, self.height() as i32)
}
pub fn is_snapshot_saved(&self, id: SnapshotId) -> bool {
self.saved_snapshot == Some(id)
}
fn saved(&mut self, id: SnapshotId, path: PathBuf) {
self.file_status = FileStatus::Saved(path);
self.saved_snapshot = Some(id);
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum FileStatus {
NoFile,
New(PathBuf),
Saved(PathBuf),
Modified(PathBuf),
}
impl ToString for FileStatus {
fn to_string(&self) -> String {
match self {
FileStatus::NoFile => String::new(),
FileStatus::Saved(ref path) => format!("{}", path.display()),
FileStatus::New(ref path) => format!("{} [new]", path.display()),
FileStatus::Modified(ref path) => format!("{} [modified]", path.display()),
}
}
}
#[derive(Debug)]
pub struct ViewManager {
pub active_id: ViewId,
views: BTreeMap<ViewId, View>,
next_id: ViewId,
lru: VecDeque<ViewId>,
}
impl ViewManager {
const MAX_LRU: usize = Session::MAX_VIEWS;
pub fn new() -> Self {
Self {
active_id: ViewId::default(),
next_id: ViewId(1),
views: BTreeMap::new(),
lru: VecDeque::new(),
}
}
pub fn add(&mut self, fs: FileStatus, w: u32, h: u32) -> ViewId {
let id = self.gen_id();
let view = View::new(id, fs, w, h);
self.views.insert(id, view);
id
}
pub fn remove(&mut self, id: ViewId) {
assert!(!self.lru.is_empty());
self.views.remove(&id);
self.lru.retain(|v| *v != id);
if let Some(v) = self.recent() {
self.activate(v);
} else {
self.active_id = ViewId::default();
}
}
pub fn recent(&self) -> Option<ViewId> {
self.lru.front().cloned()
}
pub fn active(&self) -> Option<&View> {
self.views.get(&self.active_id)
}
pub fn active_mut(&mut self) -> Option<&mut View> {
self.views.get_mut(&self.active_id)
}
pub fn activate(&mut self, id: ViewId) {
debug_assert!(
self.views.contains_key(&id),
"the view being activated exists"
);
if self.active_id == id {
return;
}
self.active_id = id;
self.lru.push_front(id);
self.lru.truncate(Self::MAX_LRU);
}
pub fn iter_mut(&mut self) -> btree_map::IterMut<'_, ViewId, View> {
self.views.iter_mut()
}
pub fn get_mut(&mut self, id: ViewId) -> Option<&mut View> {
self.views.get_mut(&id)
}
fn gen_id(&mut self) -> ViewId {
let ViewId(id) = self.next_id;
self.next_id = ViewId(id + 1);
ViewId(id)
}
}
impl Deref for ViewManager {
type Target = BTreeMap<ViewId, View>;
fn deref(&self) -> &Self::Target {
&self.views
}
}