commit
5adefdf661
@ -36,6 +36,9 @@ iced_web = { version = "0.1.0-alpha", path = "web" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
directories = "2.0"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
wasm-bindgen = "0.2.51"
|
wasm-bindgen = "0.2.51"
|
||||||
|
@ -6,3 +6,10 @@ edition = "2018"
|
|||||||
description = "The essential concepts of Iced"
|
description = "The essential concepts of Iced"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/hecrj/iced"
|
repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Exposes a future-based `Command` type
|
||||||
|
command = ["futures"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = { version = "0.3", optional = true }
|
||||||
|
49
core/src/command.rs
Normal file
49
core/src/command.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use futures::future::{BoxFuture, Future, FutureExt};
|
||||||
|
|
||||||
|
pub struct Command<T> {
|
||||||
|
futures: Vec<BoxFuture<'static, T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Command<T> {
|
||||||
|
pub fn none() -> Self {
|
||||||
|
Self {
|
||||||
|
futures: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn perform<A>(
|
||||||
|
future: impl Future<Output = T> + 'static + Send,
|
||||||
|
f: impl Fn(T) -> A + 'static + Send,
|
||||||
|
) -> Command<A> {
|
||||||
|
Command {
|
||||||
|
futures: vec![future.map(f).boxed()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn batch(commands: impl Iterator<Item = Command<T>>) -> Self {
|
||||||
|
Self {
|
||||||
|
futures: commands.flat_map(|command| command.futures).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn futures(self) -> Vec<BoxFuture<'static, T>> {
|
||||||
|
self.futures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, A> From<A> for Command<T>
|
||||||
|
where
|
||||||
|
A: Future<Output = T> + 'static + Send,
|
||||||
|
{
|
||||||
|
fn from(future: A) -> Self {
|
||||||
|
Self {
|
||||||
|
futures: vec![future.boxed()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::fmt::Debug for Command<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Command").finish()
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ pub mod widget;
|
|||||||
mod align;
|
mod align;
|
||||||
mod background;
|
mod background;
|
||||||
mod color;
|
mod color;
|
||||||
|
#[cfg(feature = "command")]
|
||||||
|
mod command;
|
||||||
mod font;
|
mod font;
|
||||||
mod length;
|
mod length;
|
||||||
mod point;
|
mod point;
|
||||||
@ -12,6 +14,8 @@ mod vector;
|
|||||||
pub use align::Align;
|
pub use align::Align;
|
||||||
pub use background::Background;
|
pub use background::Background;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
|
#[cfg(feature = "command")]
|
||||||
|
pub use command::Command;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
pub use point::Point;
|
pub use point::Point;
|
||||||
|
@ -80,7 +80,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub is_focused: bool,
|
pub is_focused: bool,
|
||||||
cursor_position: usize,
|
cursor_position: usize,
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, Align, Application, Button, Container, Element, Image,
|
button, scrollable, Align, Application, Button, Command, Container,
|
||||||
Length, Scrollable, Text,
|
Element, Image, Length, Scrollable, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
Example::default().run()
|
Example::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -25,16 +25,22 @@ pub enum Message {
|
|||||||
impl Application for Example {
|
impl Application for Example {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
|
fn new() -> (Example, Command<Message>) {
|
||||||
|
(Example::default(), Command::none())
|
||||||
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("Scroll - Iced")
|
String::from("Scroll - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::AddItem => {
|
Message::AddItem => {
|
||||||
self.item_count += 1;
|
self.item_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, text::HorizontalAlignment, text_input, Align,
|
button, scrollable, text::HorizontalAlignment, text_input, Align,
|
||||||
Application, Background, Button, Checkbox, Color, Column, Container,
|
Application, Background, Button, Checkbox, Color, Column, Command,
|
||||||
Element, Font, Length, Row, Scrollable, Text, TextInput,
|
Container, Element, Font, Length, Row, Scrollable, Text, TextInput,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
Todos::default().run()
|
Todos::run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Todos {
|
||||||
|
Loading,
|
||||||
|
Loaded(State),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Todos {
|
struct State {
|
||||||
scroll: scrollable::State,
|
scroll: scrollable::State,
|
||||||
input: text_input::State,
|
input: text_input::State,
|
||||||
input_value: String,
|
input_value: String,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
tasks: Vec<Task>,
|
tasks: Vec<Task>,
|
||||||
controls: Controls,
|
controls: Controls,
|
||||||
|
dirty: bool,
|
||||||
|
saving: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
enum Message {
|
||||||
|
Loaded(Result<SavedState, LoadError>),
|
||||||
|
Saved(Result<(), SaveError>),
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
CreateTask,
|
CreateTask,
|
||||||
FilterChanged(Filter),
|
FilterChanged(Filter),
|
||||||
@ -29,108 +40,180 @@ pub enum Message {
|
|||||||
impl Application for Todos {
|
impl Application for Todos {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn new() -> (Todos, Command<Message>) {
|
||||||
String::from("Todos - Iced")
|
(
|
||||||
|
Todos::Loading,
|
||||||
|
Command::perform(SavedState::load(), Message::Loaded),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
fn title(&self) -> String {
|
||||||
match message {
|
let dirty = match self {
|
||||||
Message::InputChanged(value) => {
|
Todos::Loading => false,
|
||||||
self.input_value = value;
|
Todos::Loaded(state) => state.dirty,
|
||||||
}
|
};
|
||||||
Message::CreateTask => {
|
|
||||||
if !self.input_value.is_empty() {
|
format!("Todos{} - Iced", if dirty { "*" } else { "" })
|
||||||
self.tasks.push(Task::new(self.input_value.clone()));
|
}
|
||||||
self.input_value.clear();
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
match self {
|
||||||
|
Todos::Loading => {
|
||||||
|
match message {
|
||||||
|
Message::Loaded(Ok(state)) => {
|
||||||
|
*self = Todos::Loaded(State {
|
||||||
|
input_value: state.input_value,
|
||||||
|
filter: state.filter,
|
||||||
|
tasks: state.tasks,
|
||||||
|
..State::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Message::Loaded(Err(_)) => {
|
||||||
|
*self = Todos::Loaded(State::default());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::FilterChanged(filter) => {
|
Todos::Loaded(state) => {
|
||||||
self.filter = filter;
|
let mut saved = false;
|
||||||
}
|
|
||||||
Message::TaskMessage(i, TaskMessage::Delete) => {
|
match message {
|
||||||
self.tasks.remove(i);
|
Message::InputChanged(value) => {
|
||||||
}
|
state.input_value = value;
|
||||||
Message::TaskMessage(i, task_message) => {
|
}
|
||||||
if let Some(task) = self.tasks.get_mut(i) {
|
Message::CreateTask => {
|
||||||
task.update(task_message);
|
if !state.input_value.is_empty() {
|
||||||
|
state
|
||||||
|
.tasks
|
||||||
|
.push(Task::new(state.input_value.clone()));
|
||||||
|
state.input_value.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::FilterChanged(filter) => {
|
||||||
|
state.filter = filter;
|
||||||
|
}
|
||||||
|
Message::TaskMessage(i, TaskMessage::Delete) => {
|
||||||
|
state.tasks.remove(i);
|
||||||
|
}
|
||||||
|
Message::TaskMessage(i, task_message) => {
|
||||||
|
if let Some(task) = state.tasks.get_mut(i) {
|
||||||
|
task.update(task_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Saved(_) => {
|
||||||
|
state.saving = false;
|
||||||
|
saved = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !saved {
|
||||||
|
state.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.dirty && !state.saving {
|
||||||
|
state.dirty = false;
|
||||||
|
state.saving = true;
|
||||||
|
|
||||||
|
Command::perform(
|
||||||
|
SavedState {
|
||||||
|
input_value: state.input_value.clone(),
|
||||||
|
filter: state.filter,
|
||||||
|
tasks: state.tasks.clone(),
|
||||||
|
}
|
||||||
|
.save(),
|
||||||
|
Message::Saved,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg!(self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
let Todos {
|
match self {
|
||||||
scroll,
|
Todos::Loading => loading_message(),
|
||||||
input,
|
Todos::Loaded(State {
|
||||||
input_value,
|
scroll,
|
||||||
filter,
|
input,
|
||||||
tasks,
|
input_value,
|
||||||
controls,
|
filter,
|
||||||
} = self;
|
tasks,
|
||||||
|
controls,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let title = Text::new("todos")
|
||||||
|
.size(100)
|
||||||
|
.color([0.5, 0.5, 0.5])
|
||||||
|
.horizontal_alignment(HorizontalAlignment::Center);
|
||||||
|
|
||||||
let title = Text::new("todos")
|
let input = TextInput::new(
|
||||||
.size(100)
|
input,
|
||||||
.color([0.5, 0.5, 0.5])
|
"What needs to be done?",
|
||||||
.horizontal_alignment(HorizontalAlignment::Center);
|
input_value,
|
||||||
|
Message::InputChanged,
|
||||||
|
)
|
||||||
|
.padding(15)
|
||||||
|
.size(30)
|
||||||
|
.on_submit(Message::CreateTask);
|
||||||
|
|
||||||
let input = TextInput::new(
|
let controls = controls.view(&tasks, *filter);
|
||||||
input,
|
let filtered_tasks =
|
||||||
"What needs to be done?",
|
tasks.iter().filter(|task| filter.matches(task));
|
||||||
input_value,
|
|
||||||
Message::InputChanged,
|
|
||||||
)
|
|
||||||
.padding(15)
|
|
||||||
.size(30)
|
|
||||||
.on_submit(Message::CreateTask);
|
|
||||||
|
|
||||||
let controls = controls.view(&tasks, *filter);
|
let tasks: Element<_> = if filtered_tasks.count() > 0 {
|
||||||
let filtered_tasks = tasks.iter().filter(|task| filter.matches(task));
|
tasks
|
||||||
|
.iter_mut()
|
||||||
let tasks: Element<_> =
|
.enumerate()
|
||||||
if filtered_tasks.count() > 0 {
|
.filter(|(_, task)| filter.matches(task))
|
||||||
tasks
|
.fold(Column::new().spacing(20), |column, (i, task)| {
|
||||||
.iter_mut()
|
column.push(task.view().map(move |message| {
|
||||||
.enumerate()
|
Message::TaskMessage(i, message)
|
||||||
.filter(|(_, task)| filter.matches(task))
|
}))
|
||||||
.fold(Column::new().spacing(20), |column, (i, task)| {
|
})
|
||||||
column.push(task.view().map(move |message| {
|
.into()
|
||||||
Message::TaskMessage(i, message)
|
} else {
|
||||||
}))
|
empty_message(match filter {
|
||||||
|
Filter::All => "You have not created a task yet...",
|
||||||
|
Filter::Active => "All your tasks are done! :D",
|
||||||
|
Filter::Completed => {
|
||||||
|
"You have not completed a task yet..."
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = Column::new()
|
||||||
|
.max_width(800)
|
||||||
|
.spacing(20)
|
||||||
|
.push(title)
|
||||||
|
.push(input)
|
||||||
|
.push(controls)
|
||||||
|
.push(tasks);
|
||||||
|
|
||||||
|
Scrollable::new(scroll)
|
||||||
|
.padding(40)
|
||||||
|
.push(
|
||||||
|
Container::new(content).width(Length::Fill).center_x(),
|
||||||
|
)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
}
|
||||||
empty_message(match filter {
|
}
|
||||||
Filter::All => "You have not created a task yet...",
|
|
||||||
Filter::Active => "All your tasks are done! :D",
|
|
||||||
Filter::Completed => "You have not completed a task yet...",
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = Column::new()
|
|
||||||
.max_width(800)
|
|
||||||
.spacing(20)
|
|
||||||
.push(title)
|
|
||||||
.push(input)
|
|
||||||
.push(controls)
|
|
||||||
.push(tasks);
|
|
||||||
|
|
||||||
Scrollable::new(scroll)
|
|
||||||
.padding(40)
|
|
||||||
.push(Container::new(content).width(Length::Fill).center_x())
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct Task {
|
struct Task {
|
||||||
description: String,
|
description: String,
|
||||||
completed: bool,
|
completed: bool,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
state: TaskState,
|
state: TaskState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TaskState {
|
pub enum TaskState {
|
||||||
Idle {
|
Idle {
|
||||||
edit_button: button::State,
|
edit_button: button::State,
|
||||||
@ -141,6 +224,14 @@ pub enum TaskState {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TaskState {
|
||||||
|
fn default() -> Self {
|
||||||
|
TaskState::Idle {
|
||||||
|
edit_button: button::State::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TaskMessage {
|
pub enum TaskMessage {
|
||||||
Completed(bool),
|
Completed(bool),
|
||||||
@ -249,7 +340,7 @@ impl Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Controls {
|
pub struct Controls {
|
||||||
all_button: button::State,
|
all_button: button::State,
|
||||||
active_button: button::State,
|
active_button: button::State,
|
||||||
@ -318,7 +409,7 @@ impl Controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Filter {
|
pub enum Filter {
|
||||||
All,
|
All,
|
||||||
Active,
|
Active,
|
||||||
@ -341,6 +432,18 @@ impl Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn loading_message() -> Element<'static, Message> {
|
||||||
|
Container::new(
|
||||||
|
Text::new("Loading...")
|
||||||
|
.horizontal_alignment(HorizontalAlignment::Center)
|
||||||
|
.size(50),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_y()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn empty_message(message: &str) -> Element<'static, Message> {
|
fn empty_message(message: &str) -> Element<'static, Message> {
|
||||||
Container::new(
|
Container::new(
|
||||||
Text::new(message)
|
Text::new(message)
|
||||||
@ -375,3 +478,80 @@ fn edit_icon() -> Text {
|
|||||||
fn delete_icon() -> Text {
|
fn delete_icon() -> Text {
|
||||||
icon('\u{F1F8}')
|
icon('\u{F1F8}')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persistence
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct SavedState {
|
||||||
|
input_value: String,
|
||||||
|
filter: Filter,
|
||||||
|
tasks: Vec<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum LoadError {
|
||||||
|
FileError,
|
||||||
|
FormatError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum SaveError {
|
||||||
|
DirectoryError,
|
||||||
|
FileError,
|
||||||
|
WriteError,
|
||||||
|
FormatError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SavedState {
|
||||||
|
fn path() -> std::path::PathBuf {
|
||||||
|
let mut path = if let Some(project_dirs) =
|
||||||
|
directories::ProjectDirs::from("rs", "Iced", "Todos")
|
||||||
|
{
|
||||||
|
project_dirs.data_dir().into()
|
||||||
|
} else {
|
||||||
|
std::env::current_dir()
|
||||||
|
.expect("The current directory is not accessible")
|
||||||
|
};
|
||||||
|
|
||||||
|
path.push("todos.json");
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load() -> Result<SavedState, LoadError> {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let mut contents = String::new();
|
||||||
|
|
||||||
|
let mut file = std::fs::File::open(Self::path())
|
||||||
|
.map_err(|_| LoadError::FileError)?;
|
||||||
|
|
||||||
|
file.read_to_string(&mut contents)
|
||||||
|
.map_err(|_| LoadError::FileError)?;
|
||||||
|
|
||||||
|
serde_json::from_str(&contents).map_err(|_| LoadError::FormatError)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save(self) -> Result<(), SaveError> {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&self)
|
||||||
|
.map_err(|_| SaveError::FormatError)?;
|
||||||
|
|
||||||
|
let path = Self::path();
|
||||||
|
let dir = path.parent().ok_or(SaveError::DirectoryError)?;
|
||||||
|
|
||||||
|
std::fs::create_dir_all(dir).map_err(|_| SaveError::DirectoryError)?;
|
||||||
|
|
||||||
|
let mut file =
|
||||||
|
std::fs::File::create(path).map_err(|_| SaveError::FileError)?;
|
||||||
|
|
||||||
|
file.write_all(json.as_bytes())
|
||||||
|
.map_err(|_| SaveError::WriteError)?;
|
||||||
|
|
||||||
|
// This is a simple way to save at most once every couple seconds
|
||||||
|
// We will be able to get rid of it once we implement event subscriptions
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, slider, text::HorizontalAlignment, text_input,
|
button, scrollable, slider, text::HorizontalAlignment, text_input,
|
||||||
Application, Background, Button, Checkbox, Color, Column, Container,
|
Application, Background, Button, Checkbox, Color, Column, Command,
|
||||||
Element, Image, Length, Radio, Row, Scrollable, Slider, Text, TextInput,
|
Container, Element, Image, Length, Radio, Row, Scrollable, Slider, Text,
|
||||||
|
TextInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
Tour::new().run()
|
Tour::run()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tour {
|
pub struct Tour {
|
||||||
@ -18,26 +19,27 @@ pub struct Tour {
|
|||||||
debug: bool,
|
debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tour {
|
|
||||||
pub fn new() -> Tour {
|
|
||||||
Tour {
|
|
||||||
steps: Steps::new(),
|
|
||||||
scroll: scrollable::State::new(),
|
|
||||||
back_button: button::State::new(),
|
|
||||||
next_button: button::State::new(),
|
|
||||||
debug: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application for Tour {
|
impl Application for Tour {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
|
fn new() -> (Tour, Command<Message>) {
|
||||||
|
(
|
||||||
|
Tour {
|
||||||
|
steps: Steps::new(),
|
||||||
|
scroll: scrollable::State::new(),
|
||||||
|
back_button: button::State::new(),
|
||||||
|
next_button: button::State::new(),
|
||||||
|
debug: true,
|
||||||
|
},
|
||||||
|
Command::none(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
format!("{} - Iced", self.steps.title())
|
format!("{} - Iced", self.steps.title())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, event: Message) {
|
fn update(&mut self, event: Message) -> Command<Message> {
|
||||||
match event {
|
match event {
|
||||||
Message::BackPressed => {
|
Message::BackPressed => {
|
||||||
self.steps.go_back();
|
self.steps.go_back();
|
||||||
@ -49,6 +51,8 @@ impl Application for Tour {
|
|||||||
self.steps.update(step_msg, &mut self.debug);
|
self.steps.update(step_msg, &mut self.debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
@ -8,6 +8,6 @@ license = "MIT"
|
|||||||
repository = "https://github.com/hecrj/iced"
|
repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.1.0-alpha", path = "../core" }
|
iced_core = { version = "0.1.0-alpha", path = "../core", features = ["command"] }
|
||||||
twox-hash = "1.5"
|
twox-hash = "1.5"
|
||||||
raw-window-handle = "0.3"
|
raw-window-handle = "0.3"
|
||||||
|
@ -216,7 +216,7 @@ mod size;
|
|||||||
mod user_interface;
|
mod user_interface;
|
||||||
|
|
||||||
pub use iced_core::{
|
pub use iced_core::{
|
||||||
Align, Background, Color, Font, Length, Point, Rectangle, Vector,
|
Align, Background, Color, Command, Font, Length, Point, Rectangle, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
|
22
src/lib.rs
22
src/lib.rs
@ -4,21 +4,23 @@ mod platform;
|
|||||||
|
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
|
|
||||||
pub trait Application {
|
pub trait Application: Sized {
|
||||||
type Message: std::fmt::Debug;
|
type Message: std::fmt::Debug + Send;
|
||||||
|
|
||||||
|
fn new() -> (Self, Command<Self::Message>);
|
||||||
|
|
||||||
fn title(&self) -> String;
|
fn title(&self) -> String;
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message);
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Self::Message>;
|
fn view(&mut self) -> Element<Self::Message>;
|
||||||
|
|
||||||
fn run(self)
|
fn run()
|
||||||
where
|
where
|
||||||
Self: 'static + Sized,
|
Self: 'static + Sized,
|
||||||
{
|
{
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
iced_winit::Application::run(Instance(self));
|
<Instance<Self> as iced_winit::Application>::run();
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
iced_web::Application::run(Instance(self));
|
iced_web::Application::run(Instance(self));
|
||||||
@ -35,12 +37,18 @@ where
|
|||||||
type Renderer = Renderer;
|
type Renderer = Renderer;
|
||||||
type Message = A::Message;
|
type Message = A::Message;
|
||||||
|
|
||||||
|
fn new() -> (Self, Command<A::Message>) {
|
||||||
|
let (app, command) = A::new();
|
||||||
|
|
||||||
|
(Instance(app), command)
|
||||||
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
self.0.title()
|
self.0.title()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) {
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||||
self.0.update(message);
|
self.0.update(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Self::Message> {
|
fn view(&mut self) -> Element<Self::Message> {
|
||||||
|
@ -2,8 +2,8 @@ pub use iced_wgpu::{Primitive, Renderer};
|
|||||||
|
|
||||||
pub use iced_winit::{
|
pub use iced_winit::{
|
||||||
button, scrollable, slider, text, text_input, winit, Align, Background,
|
button, scrollable, slider, text, text_input, winit, Align, Background,
|
||||||
Checkbox, Color, Font, Image, Length, Radio, Scrollable, Slider, Text,
|
Checkbox, Color, Command, Font, Image, Length, Radio, Scrollable, Slider,
|
||||||
TextInput,
|
Text, TextInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>;
|
pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>;
|
||||||
|
@ -13,4 +13,5 @@ debug = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
iced_native = { version = "0.1.0-alpha", path = "../native" }
|
iced_native = { version = "0.1.0-alpha", path = "../native" }
|
||||||
winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"}
|
winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"}
|
||||||
|
futures = { version = "0.3", features = ["thread-pool"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -2,24 +2,26 @@ use crate::{
|
|||||||
conversion,
|
conversion,
|
||||||
input::{keyboard, mouse},
|
input::{keyboard, mouse},
|
||||||
renderer::{Target, Windowed},
|
renderer::{Target, Windowed},
|
||||||
Cache, Container, Debug, Element, Event, Length, MouseCursor,
|
Cache, Command, Container, Debug, Element, Event, Length, MouseCursor,
|
||||||
UserInterface,
|
UserInterface,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Application {
|
pub trait Application: Sized {
|
||||||
type Renderer: Windowed;
|
type Renderer: Windowed;
|
||||||
|
|
||||||
type Message: std::fmt::Debug;
|
type Message: std::fmt::Debug + Send;
|
||||||
|
|
||||||
|
fn new() -> (Self, Command<Self::Message>);
|
||||||
|
|
||||||
fn title(&self) -> String;
|
fn title(&self) -> String;
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message);
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Self::Message, Self::Renderer>;
|
fn view(&mut self) -> Element<Self::Message, Self::Renderer>;
|
||||||
|
|
||||||
fn run(mut self)
|
fn run()
|
||||||
where
|
where
|
||||||
Self: 'static + Sized,
|
Self: 'static,
|
||||||
{
|
{
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{self, WindowEvent},
|
event::{self, WindowEvent},
|
||||||
@ -28,10 +30,18 @@ pub trait Application {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut debug = Debug::new();
|
let mut debug = Debug::new();
|
||||||
let mut title = self.title();
|
|
||||||
|
|
||||||
debug.startup_started();
|
debug.startup_started();
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::with_user_event();
|
||||||
|
let proxy = event_loop.create_proxy();
|
||||||
|
let mut thread_pool =
|
||||||
|
futures::executor::ThreadPool::new().expect("Create thread pool");
|
||||||
|
let mut external_messages = Vec::new();
|
||||||
|
|
||||||
|
let (mut application, init_command) = Self::new();
|
||||||
|
spawn(init_command, &mut thread_pool, &proxy);
|
||||||
|
|
||||||
|
let mut title = application.title();
|
||||||
|
|
||||||
// TODO: Ask for window settings and configure this properly
|
// TODO: Ask for window settings and configure this properly
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
@ -59,7 +69,7 @@ pub trait Application {
|
|||||||
|
|
||||||
debug.layout_started();
|
debug.layout_started();
|
||||||
let user_interface = UserInterface::build(
|
let user_interface = UserInterface::build(
|
||||||
document(&mut self, size, &mut debug),
|
document(&mut application, size, &mut debug),
|
||||||
Cache::default(),
|
Cache::default(),
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
);
|
);
|
||||||
@ -85,15 +95,16 @@ pub trait Application {
|
|||||||
// handled.
|
// handled.
|
||||||
debug.layout_started();
|
debug.layout_started();
|
||||||
let mut user_interface = UserInterface::build(
|
let mut user_interface = UserInterface::build(
|
||||||
document(&mut self, size, &mut debug),
|
document(&mut application, size, &mut debug),
|
||||||
cache.take().unwrap(),
|
cache.take().unwrap(),
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
);
|
);
|
||||||
debug.layout_finished();
|
debug.layout_finished();
|
||||||
|
|
||||||
debug.event_processing_started();
|
debug.event_processing_started();
|
||||||
let messages =
|
let mut messages =
|
||||||
user_interface.update(&renderer, events.drain(..));
|
user_interface.update(&renderer, events.drain(..));
|
||||||
|
messages.extend(external_messages.drain(..));
|
||||||
debug.event_processing_finished();
|
debug.event_processing_finished();
|
||||||
|
|
||||||
if messages.is_empty() {
|
if messages.is_empty() {
|
||||||
@ -113,12 +124,14 @@ pub trait Application {
|
|||||||
debug.log_message(&message);
|
debug.log_message(&message);
|
||||||
|
|
||||||
debug.update_started();
|
debug.update_started();
|
||||||
self.update(message);
|
let command = application.update(message);
|
||||||
|
|
||||||
|
spawn(command, &mut thread_pool, &proxy);
|
||||||
debug.update_finished();
|
debug.update_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update window title
|
// Update window title
|
||||||
let new_title = self.title();
|
let new_title = application.title();
|
||||||
|
|
||||||
if title != new_title {
|
if title != new_title {
|
||||||
window.set_title(&new_title);
|
window.set_title(&new_title);
|
||||||
@ -128,7 +141,7 @@ pub trait Application {
|
|||||||
|
|
||||||
debug.layout_started();
|
debug.layout_started();
|
||||||
let user_interface = UserInterface::build(
|
let user_interface = UserInterface::build(
|
||||||
document(&mut self, size, &mut debug),
|
document(&mut application, size, &mut debug),
|
||||||
temp_cache,
|
temp_cache,
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
);
|
);
|
||||||
@ -143,6 +156,9 @@ pub trait Application {
|
|||||||
|
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
event::Event::UserEvent(message) => {
|
||||||
|
external_messages.push(message);
|
||||||
|
}
|
||||||
event::Event::RedrawRequested(_) => {
|
event::Event::RedrawRequested(_) => {
|
||||||
debug.render_started();
|
debug.render_started();
|
||||||
|
|
||||||
@ -288,3 +304,25 @@ where
|
|||||||
.height(Length::Units(size.height.round() as u16))
|
.height(Length::Units(size.height.round() as u16))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spawn<Message: Send>(
|
||||||
|
command: Command<Message>,
|
||||||
|
thread_pool: &mut futures::executor::ThreadPool,
|
||||||
|
proxy: &winit::event_loop::EventLoopProxy<Message>,
|
||||||
|
) {
|
||||||
|
use futures::FutureExt;
|
||||||
|
|
||||||
|
let futures = command.futures();
|
||||||
|
|
||||||
|
for future in futures {
|
||||||
|
let proxy = proxy.clone();
|
||||||
|
|
||||||
|
let future = future.map(move |message| {
|
||||||
|
proxy
|
||||||
|
.send_event(message)
|
||||||
|
.expect("Send command result to event loop");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread_pool.spawn_ok(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user