Skip to content

Commit

Permalink
Add missing documentation to egui_router
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Jul 4, 2024
1 parent bea928d commit c8a03a9
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 69 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/egui_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# egui_router changelog

## 0.1.1

- Added missing documentation

## 0.1.0

- Initial release
1 change: 1 addition & 0 deletions crates/egui_router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ egui_inbox.workspace = true
egui_suspense = { workspace = true, optional = true }

matchit = "0.8"
thiserror = "1"

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["History", "PopStateEvent", "HtmlCollection"] }
Expand Down
8 changes: 4 additions & 4 deletions crates/egui_router/examples/router_minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,28 @@ async fn main() -> eframe::Result<()> {
}

fn home(_request: Request<AppState>) -> impl Route<AppState> {
|ui: &mut Ui, inbox: &mut UiInbox<RouterMessage>| {
|ui: &mut Ui, state: &mut AppState| {
background(ui, ui.style().visuals.faint_bg_color, |ui| {
ui.heading("Home!");

ui.label("Navigate to post:");

if ui.link("Post 1").clicked() {
inbox
state
.sender()
.send(RouterMessage::Navigate("/post/1".to_string()))
.ok();
}

if ui.link("Post 2").clicked() {
inbox
state
.sender()
.send(RouterMessage::Navigate("/post/2".to_string()))
.ok();
}

if ui.link("Invalid Post").clicked() {
inbox
state
.sender()
.send(RouterMessage::Navigate("/post/".to_string()))
.ok();
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_router/src/async_route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::Route;
use egui::Ui;
use egui_suspense::EguiSuspense;

pub struct AsyncRoute<State> {
pub(crate) struct AsyncRoute<State> {
pub suspense: EguiSuspense<Box<dyn Route<State> + Send + Sync>, HandlerError>,
}

Expand Down
46 changes: 18 additions & 28 deletions crates/egui_router/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
use crate::{Request, Route};
use std::fmt::Display;

#[derive(Debug)]
/// Error returned from a [Handler]
#[derive(Debug, thiserror::Error)]
pub enum HandlerError {
/// Not found error
#[error("Page not found")]
NotFound,
/// Custom error message
#[error("{0}")]
Message(String),
Error(Box<dyn std::error::Error + Send + Sync>),
}

impl Display for HandlerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Message(msg) => write!(f, "{}", msg),
Self::NotFound => write!(f, "Handler not found"),
Self::Error(err) => write!(f, "Handler error: {}", err),
}
}
/// Boxed error
#[error("Handler error: {0}")]
Boxed(Box<dyn std::error::Error + Send + Sync>),
}

/// Handler Result type
pub type HandlerResult<T = ()> = Result<T, HandlerError>;

impl<T: std::error::Error + Send + Sync + 'static> From<T> for HandlerError {
fn from(err: T) -> Self {
Self::Error(Box::new(err))
}
}

/// Trait for a route handler.
// The args argument is just so we can implement multiple specializations, like explained here:
// https://geo-ant.github.io/blog/2021/rust-traits-and-variadic-functions/
pub trait MakeHandler<State, Args> {
fn handle(&mut self, state: Request<State>) -> HandlerResult<Box<dyn Route<State>>>;
}

pub type Handler<State> = Box<dyn FnMut(Request<State>) -> HandlerResult<Box<dyn Route<State>>>>;
pub(crate) type Handler<State> =
Box<dyn FnMut(Request<State>) -> HandlerResult<Box<dyn Route<State>>>>;

impl<F, State, R> MakeHandler<State, (Request<'static, State>, ())> for F
where
Expand Down Expand Up @@ -75,17 +68,11 @@ where
}

#[cfg(feature = "async")]
pub mod async_impl {
mod async_impl {
use crate::handler::HandlerResult;
use crate::{Request, Route};
use std::collections::BTreeMap;
use crate::{OwnedRequest, Request, Route};
use std::future::Future;

pub struct OwnedRequest<State> {
pub params: BTreeMap<String, String>,
pub state: State,
}

pub trait AsyncMakeHandler<State, Args> {
fn handle(
&self,
Expand Down Expand Up @@ -153,3 +140,6 @@ pub mod async_impl {
}
}
}

#[cfg(feature = "async")]
pub use async_impl::*;
6 changes: 6 additions & 0 deletions crates/egui_router/src/history/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::window;

/// Browser history implementation
pub struct BrowserHistory {
base_href: String,
inbox: UiInbox<HistoryEvent>,
Expand All @@ -19,6 +20,11 @@ impl Default for BrowserHistory {
}

impl BrowserHistory {
/// Create a new [BrowserHistory] instance. Optionally pass a base href.
/// If no base href is passed, it will be read from the base tag in the document or default to ""
/// If you are deploying to github pages you could e.g. use `Some("/my-repo/#")` as base href
/// so that navigation only happens in the fragment and any route will load the index.html
/// (otherwise there might be a 404 error when refreshing the page).
pub fn new(base_href: Option<String>) -> Self {
let window = window().unwrap();

Expand Down
2 changes: 2 additions & 0 deletions crates/egui_router/src/history/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::history::{History, HistoryEvent, HistoryResult};
use egui::Context;
use std::iter;

/// A memory history implementation. Currently, this is a no-op, since [EguiRouter] stores the
/// history itself, but this could change in the future.
#[derive(Debug, Clone, Default)]
pub struct MemoryHistory {}

Expand Down
17 changes: 16 additions & 1 deletion crates/egui_router/src/history/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,46 @@ use crate::history;
pub use browser::BrowserHistory;
pub use memory::MemoryHistory;

/// Implement this trait to provide a custom history implementation
pub trait History {
/// Check whether there is a new HistoryEvent (a navigation occurred)
fn update(&mut self, ctx: &egui::Context) -> impl Iterator<Item = HistoryEvent> + 'static;
/// Get the currently active route
fn active_route(&self) -> Option<(String, Option<u32>)>;
/// Push a new route to the history
fn push(&mut self, url: &str, state: u32) -> HistoryResult;
/// Replace the current route in the history
fn replace(&mut self, url: &str, state: u32) -> HistoryResult;
/// Go back in the history
fn back(&mut self) -> HistoryResult;
/// Go forward in the history
fn forward(&mut self) -> HistoryResult;
}

/// Default history. Uses [BrowserHistory] on wasm32 and [MemoryHistory] otherwise
#[cfg(target_arch = "wasm32")]
pub type DefaultHistory = history::BrowserHistory;
/// Default history. Uses [BrowserHistory] on wasm32 and [MemoryHistory] otherwise
#[cfg(not(target_arch = "wasm32"))]
pub type DefaultHistory = history::MemoryHistory;

/// Result type returned by [History::update]
#[derive(Debug, Clone)]
pub struct HistoryEvent {
/// The path we are navigating to
pub location: String,
/// The state of the history
pub state: Option<u32>,
}

/// History Result type
type HistoryResult<T = ()> = Result<T, HistoryError>;

#[derive(Debug)]
/// History error
#[derive(Debug, thiserror::Error)]
pub enum HistoryError {
#[cfg(target_arch = "wasm32")]
#[error("History error: {0:?}")]
JsError(wasm_bindgen::JsValue),
}

Expand Down
59 changes: 40 additions & 19 deletions crates/egui_router/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(missing_docs)]

#[cfg(feature = "async")]
mod async_route;
mod handler;
/// History types
pub mod history;
mod route_kind;
mod router;
mod router_builder;
/// Transition types
pub mod transition;

use crate::history::HistoryError;
Expand All @@ -16,13 +22,18 @@ use std::sync::atomic::AtomicUsize;
pub use handler::{HandlerError, HandlerResult};
pub use router::EguiRouter;

#[cfg(feature = "async")]
pub use handler::async_impl::OwnedRequest;

pub trait Route<State> {
/// A route instance created by a [handler::Handler]
pub trait Route<State = ()> {
/// Render the route ui
fn ui(&mut self, ui: &mut egui::Ui, state: &mut State);
}

impl<F: FnMut(&mut Ui, &mut State), State> Route<State> for F {
fn ui(&mut self, ui: &mut egui::Ui, state: &mut State) {
self(ui, state)
}
}

static ID: AtomicUsize = AtomicUsize::new(0);

struct RouteState<State> {
Expand All @@ -32,11 +43,17 @@ struct RouteState<State> {
state: u32,
}

/// Router Result type
pub type RouterResult<T = ()> = Result<T, RouterError>;

#[derive(Debug)]
/// Router error
#[derive(Debug, thiserror::Error)]
pub enum RouterError {
/// Error when updating history
#[error("History error: {0}")]
HistoryError(HistoryError),
/// Not found error
#[error("Route not found")]
NotFound,
}

Expand All @@ -46,6 +63,7 @@ impl From<HistoryError> for RouterError {
}
}

/// Page transition configuration
#[derive(Debug, Clone)]
pub struct TransitionConfig {
duration: Option<f32>,
Expand All @@ -66,6 +84,7 @@ impl Default for TransitionConfig {
}

impl TransitionConfig {
/// Create a new transition
pub fn new(_in: impl Into<Transition>, out: impl Into<Transition>) -> Self {
Self {
_in: _in.into(),
Expand All @@ -74,10 +93,12 @@ impl TransitionConfig {
}
}

/// A iOS-like slide transition (Same as [TransitionConfig::default])
pub fn slide() -> Self {
Self::default()
}

/// A android-like fade up transition
pub fn fade_up() -> Self {
Self::new(
SlideFadeTransition(
Expand All @@ -88,19 +109,23 @@ impl TransitionConfig {
)
}

/// A basic fade transition
pub fn fade() -> Self {
Self::new(transition::FadeTransition, transition::FadeTransition)
}

/// No transition
pub fn none() -> Self {
Self::new(transition::NoTransition, transition::NoTransition)
}

/// Customise the easing function
pub fn with_easing(mut self, easing: fn(f32) -> f32) -> Self {
self.easing = easing;
self
}

/// Customise the duration
pub fn with_duration(mut self, duration: f32) -> Self {
self.duration = Some(duration);
self
Expand All @@ -112,23 +137,19 @@ struct CurrentTransition<State> {
leaving_route: Option<RouteState<State>>,
}

/// Request passed to a [handler::MakeHandler]
pub struct Request<'a, State = ()> {
/// The parsed path params
pub params: matchit::Params<'a, 'a>,
/// The custom state
pub state: &'a mut State,
}

// impl<F, Fut, State, R: 'static> Handler<State> for F
// where
// F: Fn(&mut State) -> Fut,
// Fut: std::future::Future<Output = R>,
// {
// async fn handle(&mut self, state: &mut State) -> Box<dyn Route<State>> {
// Box::new((self(state)).await)
// }
// }

impl<F: FnMut(&mut Ui, &mut State), State> Route<State> for F {
fn ui(&mut self, ui: &mut egui::Ui, state: &mut State) {
self(ui, state)
}
#[cfg(feature = "async")]
/// Owned request, passed to [handler::AsyncMakeHandler]
pub struct OwnedRequest<State = ()> {
/// The parsed path params
pub params: std::collections::BTreeMap<String, String>,
/// The custom state
pub state: State,
}
2 changes: 1 addition & 1 deletion crates/egui_router/src/route_kind.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::handler::Handler;

pub enum RouteKind<State> {
pub(crate) enum RouteKind<State> {
Route(Handler<State>),
Redirect(String),
}
Loading

0 comments on commit c8a03a9

Please sign in to comment.