Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xilem example for http cats API, requiring workers and image component #571

Merged
merged 13 commits into from
Sep 3, 2024
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ cargo update -p package_name --precise 0.1.1
Licensed under the Apache License, Version 2.0
([LICENSE](LICENSE) or <http://www.apache.org/licenses/LICENSE-2.0>)

The font file (`RobotoFlex-Subset.ttf`) in `xilem/resources/fonts/roboto_flex/` is licensed solely as documented in that folder,
(and is not licensed under the Apache License, Version 2.0).
Some files used for examples are under different licenses:

- The font file (`RobotoFlex-Subset.ttf`) in `xilem/resources/fonts/roboto_flex/` is licensed solely as documented in that folder (and is not licensed under the Apache License, Version 2.0).
- The data file (`status.csv`) in `xilem/resources/data/http_cats_status/` is licensed solely as documented in that folder (and is not licensed under the Apache License, Version 2.0).

## Contribution

Expand Down
12 changes: 11 additions & 1 deletion xilem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ license.workspace = true
repository.workspace = true
homepage.workspace = true
rust-version.workspace = true
exclude = ["/resources/fonts/roboto_flex/"]
exclude = ["/resources/fonts/roboto_flex/", "/resources/data/http_cats_status/"]

[package.metadata.docs.rs]
all-features = true
Expand All @@ -36,6 +36,16 @@ path = "examples/calc.rs"
# cdylib is required for cargo-apk
crate-type = ["cdylib"]

[[example]]
name = "http_cats"

[[example]]
name = "http_cats_android"
path = "examples/http_cats/main.rs"
# cdylib is required for cargo-apk
crate-type = ["cdylib"]


[[example]]
name = "stopwatch"

Expand Down
9 changes: 6 additions & 3 deletions xilem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ Unless you explicitly state otherwise, any contribution intentionally submitted

Licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE) or <http://www.apache.org/licenses/LICENSE-2.0>)

The font file (`RobotoFlex-Subset.ttf`) in `resources/fonts/roboto_flex/` is licensed solely as documented in that folder,
(and is not licensed under the Apache License, Version 2.0).
Note that this file is *not* distributed with the.
Some files used for examples are under different licenses:

* The font file (`RobotoFlex-Subset.ttf`) in `resources/fonts/roboto_flex/` is licensed solely as documented in that folder (and is not licensed under the Apache License, Version 2.0).
* The data file (`status.csv`) in `resources/data/http_cats_status/` is licensed solely as documented in that folder (and is not licensed under the Apache License, Version 2.0).

Note that these files are *not* distributed with the released crate.

[Masonry]: https://crates.io/crates/masonry
[Druid]: https://crates.io/crates/druid
Expand Down
4 changes: 4 additions & 0 deletions xilem/examples/http_cats/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Http Cats

An example demonstrating the use of Async web requests in Xilem to access the <https://http.cat/> API.
This also demonstrates image loading.
170 changes: 170 additions & 0 deletions xilem/examples/http_cats/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use winit::{dpi::LogicalSize, error::EventLoopError, window::Window};
use xilem::{
view::{button, flex, portal, prose, Axis, FlexExt, FlexSpacer},
Color, EventLoop, EventLoopBuilder, TextAlignment, WidgetView, Xilem,
};
use xilem_core::one_of::OneOf3;

#[derive(Clone)]
struct Status {
code: u32,
message: &'static str,
}

struct HttpCats {
statuses: Vec<Status>,
// The currently active code.
selected_code: Option<u32>,
}

impl HttpCats {
fn view(&mut self) -> impl WidgetView<HttpCats> {
let left_column = portal(flex((
prose("Status"),
self.statuses
.iter_mut()
.map(Status::list_view)
.collect::<Vec<_>>(),
)));
let info_area = if let Some(selected_code) = self.selected_code {
if let Some(selected_status) =
self.statuses.iter_mut().find(|it| it.code == selected_code)
{
OneOf3::A(selected_status.details_view())
} else {
OneOf3::B(
prose(format!(
"Status code {selected_code} selected, but this was not found."
))
.alignment(TextAlignment::Middle)
.brush(Color::YELLOW),
)
}
} else {
OneOf3::C(
prose("No selection yet made. Select an item from the sidebar to continue.")
.alignment(TextAlignment::Middle),
)
};

flex((
// Add padding to the top for Android. Still a horrible hack
FlexSpacer::Fixed(40.),
flex((left_column.flex(1.), info_area.flex(1.)))
.direction(Axis::Horizontal)
.must_fill_major_axis(true)
.flex(1.),
))
.must_fill_major_axis(true)
}
}

impl Status {
fn list_view(&mut self) -> impl WidgetView<HttpCats> {
let code = self.code;
flex((
// TODO: Reduce allocations here?
prose(self.code.to_string()),
prose(self.message),
FlexSpacer::Flex(1.),
button("Select", move |state: &mut HttpCats| {
state.selected_code = Some(code);
}),
FlexSpacer::Fixed(masonry::theme::SCROLLBAR_WIDTH),
))
.direction(Axis::Horizontal)
}

fn details_view(&mut self) -> impl WidgetView<HttpCats> {
flex((
prose(format!("HTTP Status Code: {}", self.code)),
prose(self.message).text_size(20.),
prose(format!(
"(Downloaded image from: https://http.cat/{})",
self.code
)),
prose("Copyright ©️ https://http.cat"),
))
.main_axis_alignment(xilem::view::MainAxisAlignment::Start)
.must_fill_major_axis(true)
}
}

fn run(event_loop: EventLoopBuilder) -> Result<(), EventLoopError> {
let data = HttpCats {
statuses: Status::parse_file(),
selected_code: None,
};

let app = Xilem::new(data, HttpCats::view);
let min_window_size = LogicalSize::new(200., 200.);

let window_attributes = Window::default_attributes()
.with_title("HTTP cats")
.with_resizable(true)
.with_min_inner_size(min_window_size);

app.run_windowed_in(event_loop, window_attributes)
}

impl Status {
/// Parse the supported HTTP cats.
fn parse_file() -> Vec<Self> {
let mut lines = STATUS_CODES_CSV.lines();
let first_line = lines.next();
assert_eq!(first_line, Some("code,message"));
lines.flat_map(Status::parse_single).collect()
}

fn parse_single(line: &'static str) -> Option<Self> {
let (code, message) = line.split_once(',')?;
Some(Self {
code: code.parse().ok()?,
message: message.trim(),
})
}
}

/// The status codes supported by <https://http.cat>, used under the MIT license.
/// Full details can be found in `xilem/resources/data/http_cats_status/README.md` from
/// the workspace root.
const STATUS_CODES_CSV: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/data/http_cats_status/status.csv",
));

#[cfg(not(target_os = "android"))]
#[allow(dead_code)]
// This is treated as dead code by the Android version of the example, but is actually live
// This hackery is required because Cargo doesn't care to support this use case, of one
// example which works across Android and desktop
fn main() -> Result<(), EventLoopError> {
run(EventLoop::with_user_event())
}

// Boilerplate code for android: Identical across all applications

#[cfg(target_os = "android")]
// Safety: We are following `android_activity`'s docs here
// We believe that there are no other declarations using this name in the compiled objects here
#[allow(unsafe_code)]
#[no_mangle]
fn android_main(app: winit::platform::android::activity::AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;

let mut event_loop = EventLoop::with_user_event();
event_loop.with_android_app(app);

run(event_loop).expect("Can create app");
}

// TODO: This is a hack because of how we handle our examples in Cargo.toml
// Ideally, we change Cargo to be more sensible here?
#[cfg(target_os = "android")]
#[allow(dead_code)]
fn main() {
unreachable!()
}
7 changes: 7 additions & 0 deletions xilem/resources/data/http_cats_status/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright (c) 2015 Rogério Vicente

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 changes: 9 additions & 0 deletions xilem/resources/data/http_cats_status/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Http Cats status data

The statuses from <https://http.cat>.
These were extracted from <https://github.com/httpcats/http.cat/blob/master/lib/statuses.js> on 2024-09-03,
specifically as at commit [`a92bb0415ef92179be6b4d34312956c7ad608d85`](https://github.com/httpcats/http.cat/blob/a92bb0415ef92179be6b4d34312956c7ad608d85/lib/statuses.js ).

## License

These are licensed solely under the MIT license, as found in [LICENSE](./LICENSE).
76 changes: 76 additions & 0 deletions xilem/resources/data/http_cats_status/status.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
code,message
100,Continue
101,Switching Protocols
102,Processing
103,Early Hints
200,OK
201,Created
202,Accepted
203,Non-Authoritative Information
204,No Content
206,Partial Content
205,Reset Content
207,Multi-Status
208,Already Reported
214,Transformation Applied
226,IM Used
300,Multiple Choices
301,Moved Permanently
302,Found
303,See Other
304,Not Modified
305,Use Proxy
307,Temporary Redirect
308,Permanent Redirect
400,Bad Request
401,Unauthorized
402,Payment Required
403,Forbidden
404,Not Found
405,Method Not Allowed
406,Not Acceptable
407,Proxy Authentication Required
408,Request Timeout
409,Conflict
410,Gone
411,Length Required
412,Precondition Failed
413,Payload Too Large
414,Request-URI Too Long
415,Unsupported Media Type
416,Request Range Not Satisfiable
417,Expectation Failed
418,I’m a teapot
420,Enhance Your Calm
421,Misdirected Request
422,Unprocessable Entity
423,Locked
424,Failed Dependency
425,Too Early
426,Upgrade Required
428,Precondition Required
429,Too Many Requests
431,Request Header Fields Too Large
444,No Response
450,Blocked by Windows Parental Controls
451,Unavailable For Legal Reasons
497,HTTP Request Sent to HTTPS Port
498,Token expired/invalid
499,Client Closed Request
500,Internal Server Error
501,Not Implemented
502,Bad Gateway
503,Service Unavailable
504,Gateway Timeout
506,Variant Also Negotiates
507,Insufficient Storage
508,Loop Detected
509,Bandwidth Limit Exceeded
510,Not Extended
511,Network Authentication Required
521,Web Server Is Down
522,Connection Timed Out
523,Origin Is Unreachable
525,SSL Handshake Failed
530,Site Frozen
599,Network Connect Timeout Error