1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
//! Build a responsive graphical user interface for your game.
//!
//! # Basic concepts
//! The user interface runtime in Coffee is heavily inspired by [Elm] and
//! [The Elm Architecture].
//!
//! Basically, user interfaces in Coffee are split into four different concepts:
//!
//!   * __state__ — data owned by the implementor of [`UserInterface`]
//!   * __messages__ — user interactions or meaningful events that you care
//!   about
//!   * __update logic__ — a way to react to __messages__ and update your
//!   __state__
//!   * __layout logic__ — a way to transform your __state__ into [widgets] that
//!   may produce __messages__ on user interaction
//!
//! # Getting started
//! Once you have implemented the [`Game`] trait, you can easily add a user
//! interface to your game by implementing the [`UserInterface`] trait.
//!
//! Let's take a look at a simple example with basic user interaction: an
//! interactive counter that can be incremented and decremented using two
//! different buttons.
//!
//! ```
//! use coffee::graphics::{Color, Window};
//! use coffee::ui::{button, Button, Column, Element, Renderer, Text, UserInterface};
//! # use coffee::graphics::{Frame, WindowSettings};
//! # use coffee::input::KeyboardAndMouse;
//! # use coffee::load::{loading_screen::ProgressBar, Task};
//! # use coffee::{Game, Result, Timer};
//!
//! // The state of our user interface
//! struct Counter {
//!     // The counter value
//!     value: i32,
//!
//!     // Local state of the two counter buttons
//!     // This is internal widget state that may change outside our update
//!     // logic
//!     increment_button: button::State,
//!     decrement_button: button::State,
//! }
//!
//! # impl Game for Counter {
//! #     type Input = KeyboardAndMouse;
//! #     type LoadingScreen = ProgressBar;
//! #
//! #     fn load(_window: &Window) -> Task<Counter> {
//! #         Task::succeed(|| Counter {
//! #             value: 0,
//! #             increment_button: button::State::new(),
//! #             decrement_button: button::State::new(),
//! #         })
//! #     }
//! #
//! #     fn draw(&mut self, frame: &mut Frame, _timer: &Timer) {
//! #         frame.clear(Color::BLACK);
//! #     }
//! # }
//! #
//! // The messages, user interactions that we are interested on
//! #[derive(Debug, Clone, Copy)]
//! pub enum Message {
//!     IncrementPressed,
//!     DecrementPressed,
//! }
//!
//! impl UserInterface for Counter {
//!     // We use the message enum we just defined
//!     type Message = Message;
//!
//!     // We can use the the built-in `Renderer`
//!     type Renderer = Renderer;
//!
//!     // The update logic, called when a message is produced
//!     fn react(&mut self, message: Message, _window: &mut Window) {
//!         // We update the counter value after an interaction here
//!         match message {
//!             Message::IncrementPressed => {
//!                 self.value += 1;
//!             }
//!             Message::DecrementPressed => {
//!                 self.value -= 1;
//!             }
//!         }
//!     }
//!
//!     // The layout logic, describing the different components of the user interface
//!     fn layout(&mut self, window: &Window) -> Element<Message> {
//!         // We use a column so the elements inside are laid out vertically
//!         Column::new()
//!             .push(
//!                 // The increment button. We tell it to produce an
//!                 // `IncrementPressed` message when pressed
//!                 Button::new(&mut self.increment_button, "+")
//!                     .on_press(Message::IncrementPressed),
//!             )
//!             .push(
//!                 // We show the value of the counter here
//!                 Text::new(&self.value.to_string()).size(50),
//!             )
//!             .push(
//!                 // The decrement button. We tell it to produce a
//!                 // `DecrementPressed` message when pressed
//!                 Button::new(&mut self.decrement_button, "-")
//!                     .on_press(Message::DecrementPressed),
//!             )
//!             .into() // We need to return a generic `Element`
//!     }
//! }
//! ```
//!
//! _The [`Game`] implementation is mostly irrelevant and was omitted in order to
//! keep the example short. You can find the full source code of this example
//! (and other examples too!) in the [`examples` directory on GitHub]._
//!
//! Notice how [`UserInterface::react`] focuses on processing messages and
//! updating state. On the other hand, [`UserInterface::layout`] only focuses on
//! building the user interface from the current state. This separation of
//! concerns will help you build composable user interfaces that are easy to
//! debug and test!
//!
//! # Customization
//! Coffee provides some [widgets] and a [`Renderer`] out-of-the-box. However,
//! you can build your own! Check out the [`core`] module to learn more!
//!
//! [Elm]: https://elm-lang.org
//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/
//! [`UserInterface`]: trait.UserInterface.html
//! [`UserInterface::react`]: trait.UserInterface.html#tymethod.react
//! [`UserInterface::layout`]: trait.UserInterface.html#tymethod.layout
//! [`UserInterface::Message`]: trait.UserInterface.html#associatedtype.Message
//! [widgets]: widget/index.html
//! [`Button`]: widget/button/struct.Button.html
//! [`Game`]: ../trait.Game.html
//! [`examples` directory on GitHub]: https://github.com/hecrj/coffee/tree/master/examples
//! [`Renderer`]: struct.Renderer.html
//! [`core`]: core/index.html
pub mod core;
mod renderer;
pub mod widget;

#[doc(no_inline)]
pub use self::core::{Align, Justify};
pub use renderer::{Configuration, Renderer};
pub use widget::{
    button, image, progress_bar, slider, Button, Checkbox, Image, ProgressBar,
    Radio, Slider, Text,
};

/// A [`Column`] using the built-in [`Renderer`].
///
/// [`Column`]: widget/struct.Column.html
/// [`Renderer`]: struct.Renderer.html
pub type Column<'a, Message> = widget::Column<'a, Message, Renderer>;

/// A [`Row`] using the built-in [`Renderer`].
///
/// [`Row`]: widget/struct.Row.html
/// [`Renderer`]: struct.Renderer.html
pub type Row<'a, Message> = widget::Row<'a, Message, Renderer>;

/// A [`Panel`] using the built-in [`Renderer`].
///
/// [`Panel`]: widget/panel/struct.Panel.html
/// [`Renderer`]: struct.Renderer.html
pub type Panel<'a, Message> = widget::Panel<'a, Message, Renderer>;

/// An [`Element`] using the built-in [`Renderer`].
///
/// [`Element`]: core/struct.Element.html
/// [`Renderer`]: struct.Renderer.html
pub type Element<'a, Message> = self::core::Element<'a, Message, Renderer>;

use crate::game::{self, Loop as _};
use crate::graphics::{Point, Window, WindowSettings};
use crate::input::{self, mouse, Input as _};
use crate::load::Task;
use crate::ui::core::{Event, Interface, MouseCursor, Renderer as _};
use crate::{Debug, Game, Result};
use std::convert::TryInto;

/// The user interface of your game.
///
/// Implementors of this trait must also implement [`Game`] and should hold all
/// the state of the user interface.
///
/// Be sure to read the introduction of the [`ui` module] first! It will help
/// you understand the purpose of this trait.
///
/// [`Game`]: ../trait.Game.html
/// [`ui` module]: index.html
pub trait UserInterface: Game {
    /// The type of messages handled by the user interface.
    ///
    /// Messages are produced by user interactions. The runtime feeds these
    /// messages to the [`react`] method, which updates the state of the game
    /// depending on the user interaction.
    ///
    /// The [`Message`] type should normally be an enumeration of different
    /// user interactions. For example:
    ///
    /// ```
    /// enum Message {
    ///     ButtonPressed,
    ///     CheckboxToggled(bool),
    ///     SliderChanged(f32),
    ///     // ...
    /// }
    /// ```
    ///
    /// [`react`]: #tymethod.react
    /// [`Message`]: #associatedtype.Message
    type Message;

    /// The renderer used to draw the user interface.
    ///
    /// If you just want to use the built-in widgets in Coffee, you should
    /// use the built-in [`Renderer`] type here.
    ///
    /// If you want to write your own renderer, you will need to implement the
    /// [`core::Renderer`] trait.
    ///
    /// [`Renderer`]: struct.Renderer.html
    /// [`core::Renderer`]: core/trait.Renderer.html
    type Renderer: self::core::Renderer;

    /// Reacts to a [`Message`], updating game state as needed.
    ///
    /// This method is analogous to [`Game::interact`], but it processes a
    /// [`Message`] instead of [`Game::Input`].
    ///
    /// The logic of your user interface should live here.
    ///
    /// [`Game::interact`]: ../trait.Game.html#method.interact
    /// [`Game::Input`]: ../trait.Game.html#associatedtype.Input
    /// [`Message`]: #associatedtype.Message
    fn react(&mut self, message: Self::Message, window: &mut Window);

    /// Produces the layout of the user interface.
    ///
    /// It returns an [`Element`] containing the different widgets that comprise
    /// the user interface.
    ///
    /// This method is called on every frame. The produced layout is rendered
    /// and used by the runtime to allow user interaction.
    ///
    /// [`Element`]: core/struct.Element.html
    fn layout(
        &mut self,
        window: &Window,
    ) -> self::core::Element<'_, Self::Message, Self::Renderer>;

    /// Builds the renderer configuration for the user interface.
    ///
    /// By default, it returns `Default::default()`.
    fn configuration() -> <Self::Renderer as core::Renderer>::Configuration {
        Default::default()
    }

    /// Runs the [`Game`] with a user interface.
    ///
    /// Call this method instead of [`Game::run`] once you have implemented the
    /// [`UserInterface`].
    ///
    /// [`Game`]: ../trait.Game.html
    /// [`UserInterface`]: trait.UserInterface.html
    /// [`Game::run`]: ../trait.Game.html#method.run
    fn run(window_settings: WindowSettings) -> Result<()>
    where
        Self: 'static + Sized,
    {
        Loop::<Self>::run(window_settings)
    }
}

struct Loop<UI: UserInterface> {
    renderer: UI::Renderer,
    messages: Vec<UI::Message>,
    mouse_cursor: MouseCursor,
    cache: Option<core::Cache>,
    cursor_position: Point,
    events: Vec<Event>,
}

impl<UI: UserInterface> game::Loop<UI> for Loop<UI> {
    type Attributes = UI::Renderer;

    fn new(renderer: UI::Renderer, game: &mut UI, window: &Window) -> Self {
        let cache = Interface::compute(game.layout(window), &renderer).cache();
        Loop {
            renderer,
            messages: Vec::new(),
            mouse_cursor: MouseCursor::OutOfBounds,
            cache: Some(cache),
            cursor_position: Point::new(0.0, 0.0),
            events: Vec::new(),
        }
    }

    fn load(_window: &Window) -> Task<UI::Renderer> {
        UI::Renderer::load(UI::configuration())
    }

    fn on_input(&mut self, input: &mut UI::Input, event: input::Event) {
        input.update(event);

        match event {
            input::Event::Mouse(mouse::Event::CursorMoved { x, y }) => {
                self.cursor_position = Point::new(x, y);
            }
            _ => {}
        };

        if let Some(ui_event) = Event::from_input(event) {
            self.events.push(ui_event);
        }
    }

    fn after_draw(
        &mut self,
        ui: &mut UI,
        input: &mut UI::Input,
        window: &mut Window,
        debug: &mut Debug,
    ) {
        debug.ui_started();
        let mut interface = Interface::compute_with_cache(
            ui.layout(window),
            &self.renderer,
            self.cache.take().unwrap(),
        );

        let cursor_position = self.cursor_position;
        let messages = &mut self.messages;

        self.events.drain(..).for_each(|event| {
            interface.on_event(event, cursor_position, messages)
        });

        let new_cursor = interface.draw(
            &mut self.renderer,
            &mut window.frame(),
            cursor_position,
        );

        self.cache = Some(interface.cache());

        if new_cursor != self.mouse_cursor {
            if new_cursor == MouseCursor::OutOfBounds {
                input.update(input::Event::Mouse(mouse::Event::CursorReturned));
            } else if self.mouse_cursor == MouseCursor::OutOfBounds {
                input.update(input::Event::Mouse(mouse::Event::CursorTaken));
            }

            self.mouse_cursor = new_cursor;
        }
        // Use the game cursor if cursor is not on a UI element, use the mouse cursor otherwise
        if self.mouse_cursor == MouseCursor::OutOfBounds {
            let game_cursor = ui.cursor_icon();
            window.update_cursor(game_cursor.try_into().ok());
        } else {
            window.update_cursor(Some(self.mouse_cursor.into()));
        }

        for message in messages.drain(..) {
            ui.react(message, window);
        }
        debug.ui_finished();
    }
}