diff --git a/COPYING.md b/COPYING.md
new file mode 100644
index 00000000..9f4438db
--- /dev/null
+++ b/COPYING.md
@@ -0,0 +1,27 @@
+
+Any file in this project that does not state otherwise and is not listed as an
+exception below is part of *plato* and copyright (c) 2017 Bastien Dejean
+and plato contributors.
+
+For a list of the authors see the commit log or
+https://github.com/baskerville/plato/graphs/contributors/
+
+Plato is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+See the LICENSE file for a copy of the *GNU General Public License*.
+
+
+Exceptions (free)
+-----------------
+
+Files | Author(s) | License
+--- | --- | ---
+icons/chess|[Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt)
diff --git a/artworks/chess.svg b/artworks/chess.svg
new file mode 100644
index 00000000..d5157b41
--- /dev/null
+++ b/artworks/chess.svg
@@ -0,0 +1,1374 @@
+
+
diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml
index d7bd07e0..c3a3172a 100644
--- a/crates/core/Cargo.toml
+++ b/crates/core/Cargo.toml
@@ -40,3 +40,14 @@ rand_core = "0.6.4"
rand_xoshiro = "0.6.0"
percent-encoding = "2.3.0"
chrono = { version = "0.4.30", features = ["serde", "clock"], default-features = false }
+log = "0.4.17"
+
+chess_uci = { git = "https://git.hadoly.fr/bateast/chess_uci.git", optional = true }
+chess = { version = "3.2.0", optional = true }
+env_logger = { version = "0.10.0", optional = true }
+
+
+[features]
+chess = ["dep:chess_uci", "dep:chess"]
+devel = ["dep:env_logger"]
+
diff --git a/crates/core/build.rs b/crates/core/build.rs
index e3ab3e01..3c1e9a95 100644
--- a/crates/core/build.rs
+++ b/crates/core/build.rs
@@ -16,6 +16,14 @@ fn main() {
"linux" => {
println!("cargo:rustc-link-search=target/mupdf_wrapper/Linux");
println!("cargo:rustc-link-lib=dylib=stdc++");
+ println!("cargo:rustc-link-lib=dylib=stdc++");
+ println!("cargo:rustc-link-lib=z");
+ println!("cargo:rustc-link-lib=bz2");
+ println!("cargo:rustc-link-lib=jpeg");
+ println!("cargo:rustc-link-lib=png16");
+ println!("cargo:rustc-link-lib=gumbo");
+ println!("cargo:rustc-link-lib=openjp2");
+ println!("cargo:rustc-link-lib=jbig2dec");
},
"macos" => {
println!("cargo:rustc-link-search=target/mupdf_wrapper/Darwin");
diff --git a/crates/core/rustfmt.toml b/crates/core/rustfmt.toml
new file mode 100644
index 00000000..22026e83
--- /dev/null
+++ b/crates/core/rustfmt.toml
@@ -0,0 +1,3 @@
+max_width=120
+fn_args_layout="Compressed"
+short_array_element_width_threshold=25
diff --git a/crates/core/src/color.rs b/crates/core/src/color.rs
index 09bf244f..865147a1 100644
--- a/crates/core/src/color.rs
+++ b/crates/core/src/color.rs
@@ -27,6 +27,10 @@ pub const TEXT_BUMP_LARGE: [u8; 3] = [GRAY11, BLACK, BLACK];
pub const TEXT_INVERTED_SOFT: [u8; 3] = [GRAY05, WHITE, WHITE];
pub const TEXT_INVERTED_HARD: [u8; 3] = [BLACK, WHITE, GRAY06];
+pub const CHESSBOARD_BG: u8 = KEYBOARD_BG;
+pub const CELL_BLACK: [u8; 2] = [GRAY05, GRAY07];
+pub const CELL_WHITE: [u8; 2] = [GRAY10, GRAY07];
+
pub const SEPARATOR_NORMAL: u8 = GRAY10;
pub const SEPARATOR_STRONG: u8 = GRAY07;
diff --git a/crates/core/src/document/mupdf_sys.rs b/crates/core/src/document/mupdf_sys.rs
index 5b4179ee..149430ee 100644
--- a/crates/core/src/document/mupdf_sys.rs
+++ b/crates/core/src/document/mupdf_sys.rs
@@ -3,6 +3,9 @@
use std::mem;
pub const FZ_MAX_COLORS: usize = 32;
+#[cfg(target_arch = "x86_64")]
+pub const FZ_VERSION: &str = "1.23.3";
+#[cfg(target_arch = "arm")]
pub const FZ_VERSION: &str = "1.22.2";
pub const FZ_META_INFO_AUTHOR: &str = "info:Author";
diff --git a/crates/core/src/emulator.rs b/crates/core/src/emulator.rs
index d5ac330a..88b21f50 100644
--- a/crates/core/src/emulator.rs
+++ b/crates/core/src/emulator.rs
@@ -67,6 +67,9 @@ use crate::library::Library;
use crate::font::Fonts;
use crate::app::Context;
+#[cfg(feature = "chess")]
+use crate::view::plato_chess::PlatoChess;
+
pub const APP_NAME: &str = "Plato";
const DEFAULT_ROTATION: i8 = 1;
@@ -413,6 +416,10 @@ fn main() -> Result<(), Error> {
AppCmd::Sketch => {
Box::new(Sketch::new(context.fb.rect(), &mut rq, &mut context))
},
+ #[cfg(feature = "chess")]
+ AppCmd::Chess => {
+ Box::new(PlatoChess::new(context.fb.rect(), &mut rq, &mut context))
+ },
AppCmd::Calculator => {
Box::new(Calculator::new(context.fb.rect(), &tx, &mut rq, &mut context)?)
},
diff --git a/crates/core/src/settings/mod.rs b/crates/core/src/settings/mod.rs
index 5f16f854..0adb9936 100644
--- a/crates/core/src/settings/mod.rs
+++ b/crates/core/src/settings/mod.rs
@@ -121,6 +121,8 @@ pub struct Settings {
pub reader: ReaderSettings,
pub import: ImportSettings,
pub dictionary: DictionarySettings,
+ #[cfg(feature = "chess")]
+ pub chess_engine_settings: ChessEngineSettings,
pub sketch: SketchSettings,
pub calculator: CalculatorSettings,
pub battery: BatterySettings,
@@ -174,6 +176,25 @@ pub struct ImportSettings {
pub allowed_kinds: FxHashSet,
}
+#[cfg(feature = "chess")]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub struct ChessEngineSettings {
+ pub elo: u32,
+ pub slow_motion: u32,
+ pub path: PathBuf,
+ pub font_size: f32,
+ pub margin_width: i32,
+}
+
+#[cfg(feature = "chess")]
+impl Default for ChessEngineSettings {
+ fn default() -> Self {
+ ChessEngineSettings { elo: 1500, slow_motion: 100, path: PathBuf::from("bin/stockfish"),
+ font_size: 8.0, margin_width: 2}
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct DictionarySettings {
@@ -546,6 +567,8 @@ impl Default for Settings {
reader: ReaderSettings::default(),
import: ImportSettings::default(),
dictionary: DictionarySettings::default(),
+ #[cfg(feature = "chess")]
+ chess_engine_settings: ChessEngineSettings::default(),
sketch: SketchSettings::default(),
calculator: CalculatorSettings::default(),
battery: BatterySettings::default(),
diff --git a/crates/core/src/view/clock.rs b/crates/core/src/view/clock.rs
index 55eefb88..33614845 100644
--- a/crates/core/src/view/clock.rs
+++ b/crates/core/src/view/clock.rs
@@ -17,21 +17,23 @@ pub struct Clock {
}
impl Clock {
- pub fn new(rect: &mut Rectangle, context: &mut Context) -> Clock {
- let time = Local::now();
- let format = context.settings.time_format.clone();
- let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, CURRENT_DEVICE.dpi);
- let width = font.plan(&time.format(&format).to_string(), None, None).width + font.em() as i32;
- rect.min.x = rect.max.x - width;
+ pub fn new(rect: Rectangle, context: &mut Context) -> Clock {
Clock {
id: ID_FEEDER.next(),
- rect: *rect,
+ rect,
children: Vec::new(),
- format,
- time,
+ format: context.settings.time_format.clone(),
+ time: Local::now(),
}
}
+ pub fn compute_width(context: &mut Context) -> i32 {
+ let time = Local::now();
+ let format = context.settings.time_format.clone();
+ let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, CURRENT_DEVICE.dpi);
+ font.plan(&time.format(&format).to_string(), None, None).width + font.em() as i32
+ }
+
pub fn update(&mut self, rq: &mut RenderQueue) {
self.time = Local::now();
rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
diff --git a/crates/core/src/view/common.rs b/crates/core/src/view/common.rs
index e4a5a696..0417c7b5 100644
--- a/crates/core/src/view/common.rs
+++ b/crates/core/src/view/common.rs
@@ -85,6 +85,9 @@ pub fn toggle_main_menu(view: &mut dyn View, rect: Rectangle, enable: Option,
hold_event: Option,
+ font_size: u32,
}
impl Label {
@@ -27,6 +28,7 @@ impl Label {
align,
event: None,
hold_event: None,
+ font_size: NORMAL_STYLE.size,
}
}
@@ -46,6 +48,10 @@ impl Label {
rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
}
}
+
+ pub fn set_font_size(&mut self, font_size: f32) {
+ self.font_size = (64.0 * font_size) as u32;
+ }
}
impl View for Label {
@@ -72,7 +78,8 @@ impl View for Label {
fb.draw_rectangle(&self.rect, TEXT_NORMAL[0]);
- let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
+ let mut style = NORMAL_STYLE; style.size = self.font_size;
+ let font = font_from_style(fonts, &style, dpi);
let x_height = font.x_heights.0 as i32;
let padding = font.em() as i32;
let max_width = self.rect.width() as i32 - padding;
diff --git a/crates/core/src/view/mod.rs b/crates/core/src/view/mod.rs
index 4efb369f..570aa369 100644
--- a/crates/core/src/view/mod.rs
+++ b/crates/core/src/view/mod.rs
@@ -42,6 +42,13 @@ pub mod calculator;
pub mod sketch;
pub mod touch_events;
pub mod rotation_values;
+#[cfg(feature = "chess")]
+pub mod plato_chess;
+
+#[cfg(feature = "devel")]
+use {
+ crate::device::CURRENT_DEVICE,
+};
use std::ops::{Deref, DerefMut};
use std::time::{Instant, Duration};
@@ -63,6 +70,12 @@ use crate::gesture::GestureEvent;
use self::calculator::LineOrigin;
use self::key::KeyKind;
use crate::context::Context;
+#[cfg(feature = "chess")]
+use {
+ chess::Square,
+ chess_uci::uci_command::EngineCommand,
+};
+use log::{debug, warn};
// Border thicknesses in pixels, at 300 DPI.
pub const THICKNESS_SMALL: f32 = 1.0;
@@ -294,6 +307,12 @@ pub enum Event {
Gesture(GestureEvent),
Keyboard(KeyboardEvent),
Key(KeyKind),
+ #[cfg(feature = "chess")]
+ ChessCommand(EngineCommand),
+ #[cfg(feature = "chess")]
+ ChessGo(bool),
+ #[cfg(feature = "chess")]
+ ChessCell(Square, bool),
Open(Box),
OpenHtml(String, Option),
LoadPixmap(usize),
@@ -350,6 +369,12 @@ pub enum Event {
Scroll(i32),
Save,
Guess,
+ #[cfg(feature = "chess")]
+ Reset,
+ #[cfg(feature = "chess")]
+ UpdateValue(f32),
+ #[cfg(feature = "chess")]
+ SaveAll(ViewId, Vec),
CheckBattery,
SetWifi(bool),
MightSuspend,
@@ -368,6 +393,8 @@ pub enum Event {
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum AppCmd {
Sketch,
+ #[cfg(feature = "chess")]
+ Chess,
Calculator,
Dictionary {
query: String,
@@ -408,6 +435,14 @@ pub enum ViewId {
MarginCropperMenu,
SearchMenu,
SketchMenu,
+ #[cfg(feature = "chess")]
+ ChessMenu,
+ #[cfg(feature = "chess")]
+ ChessSettings,
+ #[cfg(feature = "chess")]
+ ChessPromotion,
+ #[cfg(feature = "chess")]
+ ChessMoves,
RenameDocument,
RenameDocumentInput,
GoToPage,
@@ -444,6 +479,10 @@ pub enum SliderId {
LightWarmth,
ContrastExponent,
ContrastGray,
+ #[cfg(feature = "chess")]
+ ChessElo,
+ #[cfg(feature = "chess")]
+ ChessSlow,
}
impl SliderId {
@@ -454,6 +493,10 @@ impl SliderId {
SliderId::FontSize => "Font Size".to_string(),
SliderId::ContrastExponent => "Contrast Exponent".to_string(),
SliderId::ContrastGray => "Contrast Gray".to_string(),
+ #[cfg(feature = "chess")]
+ SliderId::ChessElo => "Elo level calibration".to_string(),
+ #[cfg(feature = "chess")]
+ SliderId::ChessSlow => "Slow motion".to_string(),
}
}
}
@@ -521,6 +564,14 @@ pub enum EntryId {
CopyTo(PathBuf, usize),
MoveTo(PathBuf, usize),
AddDirectory(PathBuf),
+ #[cfg(feature = "chess")]
+ ChessPlayer(chess::Color, chess_uci::Player),
+ #[cfg(feature = "chess")]
+ ChessPiece(chess::ChessMove),
+ #[cfg(feature = "chess")]
+ ChessEngineSettings,
+ #[cfg(feature = "chess")]
+ ChessTimeControl(u64, u64),
SelectDirectory(PathBuf),
ToggleSelectDirectory(PathBuf),
SetStatus(PathBuf, SimpleStatus),
@@ -612,6 +663,7 @@ impl EntryKind {
}
}
+#[derive(Debug)]
pub struct RenderData {
pub id: Option,
pub rect: Rectangle,
@@ -671,6 +723,12 @@ impl RenderQueue {
}
pub fn add(&mut self, data: RenderData) {
+ debug!("adding to render queue {:?}", data);
+ #[cfg(feature = "devel")]
+ if (data.rect.max.x as u32, data.rect.max.y as u32) > CURRENT_DEVICE.dims {
+ warn!("rect ({:?})out of screen limits {:?}", data.rect, CURRENT_DEVICE.dims);
+ }
+
self.entry((data.mode, data.wait)).or_insert_with(|| {
Vec::new()
}).push((data.id, data.rect));
diff --git a/crates/core/src/view/plato_chess/bottom_bar.rs b/crates/core/src/view/plato_chess/bottom_bar.rs
new file mode 100644
index 00000000..a920031c
--- /dev/null
+++ b/crates/core/src/view/plato_chess/bottom_bar.rs
@@ -0,0 +1,231 @@
+use crate::view::icon::Icon;
+use crate::view::key::KeyKind;
+use crate::view::label::Label;
+use crate::view::{Align, Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
+
+use crate::context::Context;
+use crate::device::CURRENT_DEVICE;
+use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
+use crate::framebuffer::{Framebuffer, UpdateMode};
+use crate::geom::Rectangle;
+use crate::gesture::GestureEvent;
+use crate::input::DeviceEvent;
+
+use log::debug;
+
+use chess::Color;
+
+use std::time::Duration;
+
+#[derive(Debug)]
+pub struct BottomBar {
+ id: Id,
+ rect: Rectangle,
+ children: Vec>,
+ color: Color,
+ human: bool,
+ clocks: [Duration; 2],
+}
+
+impl BottomBar {
+ pub fn new(
+ rect: Rectangle, name: &str, color: Color, human: bool, context: &mut Context,
+ ) -> BottomBar {
+ let id = ID_FEEDER.next();
+ let mut children = Vec::new();
+
+ let sizes = Self::compute_sizes(rect, context);
+
+ let icon = Icon::new(
+ match color {
+ Color::White => "wchess",
+ Color::Black => "bchess",
+ },
+ sizes[0],
+ Event::Key(KeyKind::Alternate),
+ );
+ children.push(Box::new(icon) as Box);
+
+ let icon = Icon::new("arrow-left", sizes[1], Event::Cancel);
+ children.push(Box::new(icon) as Box);
+
+ let name_label = Label::new(sizes[2], name.to_string(), Align::Center);
+ children.push(Box::new(name_label) as Box);
+
+ let mut white_time_label = Label::new(sizes[3], "––:––:––".to_string(), Align::Left(0));
+ white_time_label.set_font_size(context.settings.chess_engine_settings.font_size);
+ children.push(Box::new(white_time_label) as Box);
+ let mut black_time_label = Label::new(sizes[4], "––:––:––".to_string(), Align::Right(0));
+ black_time_label.set_font_size(context.settings.chess_engine_settings.font_size);
+ children.push(Box::new(black_time_label) as Box);
+
+ let icon = Icon::new(
+ if human { "human-chess" } else { "engine-chess" },
+ sizes[5],
+ Event::ChessGo(human),
+ );
+ children.push(Box::new(icon) as Box);
+
+ BottomBar {
+ id,
+ rect,
+ children,
+ color,
+ human,
+ clocks: [Duration::new(0, 0), Duration::new(0, 0)],
+ }
+ }
+
+ fn compute_sizes(rect: Rectangle, context: &mut Context) -> [Rectangle; 6] {
+ let side = rect.height() as i32;
+
+ let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, CURRENT_DEVICE.dpi);
+ let plan = font.plan("––:––:–– ←", None, None);
+ let time_width = 15 * plan.width / 10;
+
+ [
+ rect![rect.min, rect.min + side],
+ // undo_icon
+ rect![
+ pt!(rect.min.x + side, rect.min.y),
+ pt!(rect.min.x + 2 * side, rect.min.y + side)
+ ],
+ // labels
+ rect![
+ pt!(rect.min.x + 2 * side, rect.min.y),
+ pt!(rect.max.x - side - time_width, rect.max.y)
+ ],
+ rect![
+ pt!(rect.max.x - side - time_width, rect.min.y),
+ pt!(rect.max.x - side, rect.max.y - rect.height() as i32 / 2)
+ ],
+ rect![
+ pt!(rect.max.x - side - time_width, rect.min.y + rect.height() as i32 / 2),
+ pt!(rect.max.x - side, rect.max.y)
+ ],
+ rect![rect.max - side, rect.max],
+ ]
+ }
+
+ pub fn update_color(&mut self, color: Color, rq: &mut RenderQueue) {
+ if self.color != color {
+ let index = 0;
+ let color_rect = *self.child(index).rect();
+
+ let icon = Icon::new(
+ match color {
+ Color::White => "wchess",
+ Color::Black => "bchess",
+ },
+ color_rect,
+ Event::Key(KeyKind::Alternate),
+ );
+ rq.add(RenderData::new(icon.id(), color_rect, UpdateMode::Gui));
+ self.children[index] = Box::new(icon) as Box;
+ }
+ self.color = color;
+
+ self.update_clocks(self.clocks[0], self.clocks[1], rq)
+ }
+
+ pub fn update_player(&mut self, human: bool, rq: &mut RenderQueue) {
+ if human != self.human {
+ let index = self.len() - 1;
+ let rect = *self.child(index).rect();
+
+ let icon = Icon::new(
+ if human { "human-chess" } else { "engine-chess" },
+ rect,
+ Event::ChessGo(human),
+ );
+ rq.add(RenderData::new(icon.id(), rect, UpdateMode::Gui));
+ self.children[index] = Box::new(icon) as Box;
+ }
+ self.human = human;
+ }
+
+ pub fn update_name(&mut self, text: &str, rq: &mut RenderQueue) {
+ let name_label = self.child_mut(2).downcast_mut::