2020-11-17 23:43:58 +00:00
|
|
|
use std::sync::mpsc::Receiver;
|
|
|
|
|
2020-11-18 20:38:29 +00:00
|
|
|
struct Resource {
|
|
|
|
/// HTTP response
|
2021-09-03 19:04:43 +00:00
|
|
|
response: ehttp::Response,
|
2020-11-18 20:38:29 +00:00
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
text: Option<String>,
|
|
|
|
|
2020-11-18 20:38:29 +00:00
|
|
|
/// If set, the response was an image.
|
|
|
|
image: Option<Image>,
|
2020-12-28 18:50:48 +00:00
|
|
|
|
|
|
|
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
|
|
|
|
colored_text: Option<ColoredText>,
|
2020-11-18 20:38:29 +00:00
|
|
|
}
|
|
|
|
|
2020-11-20 18:50:47 +00:00
|
|
|
impl Resource {
|
2021-09-03 19:04:43 +00:00
|
|
|
fn from_response(response: ehttp::Response) -> Self {
|
2021-08-15 14:56:46 +00:00
|
|
|
let content_type = response.content_type().unwrap_or_default();
|
|
|
|
let image = if content_type.starts_with("image/") {
|
2020-11-20 18:50:47 +00:00
|
|
|
Image::decode(&response.bytes)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2020-12-28 18:50:48 +00:00
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
let text = response.text();
|
|
|
|
|
|
|
|
let colored_text = text
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|text| syntax_highlighting(&response, text));
|
2020-12-28 18:50:48 +00:00
|
|
|
|
|
|
|
Self {
|
|
|
|
response,
|
2021-08-15 14:56:46 +00:00
|
|
|
text,
|
2020-12-28 18:50:48 +00:00
|
|
|
image,
|
|
|
|
colored_text,
|
|
|
|
}
|
2020-11-20 18:50:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
2021-09-29 06:45:13 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
2021-08-15 14:56:46 +00:00
|
|
|
enum Method {
|
|
|
|
Get,
|
|
|
|
Post,
|
|
|
|
}
|
|
|
|
|
2021-09-29 06:45:13 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
2021-01-01 16:11:05 +00:00
|
|
|
pub struct HttpApp {
|
2020-11-17 23:43:58 +00:00
|
|
|
url: String,
|
2021-01-01 16:11:05 +00:00
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
method: Method,
|
|
|
|
|
|
|
|
request_body: String,
|
|
|
|
|
2021-09-29 06:45:13 +00:00
|
|
|
#[cfg_attr(feature = "serde", serde(skip))]
|
2021-09-03 19:04:43 +00:00
|
|
|
in_progress: Option<Receiver<Result<ehttp::Response, String>>>,
|
2021-01-01 16:11:05 +00:00
|
|
|
|
2021-09-29 06:45:13 +00:00
|
|
|
#[cfg_attr(feature = "serde", serde(skip))]
|
2020-11-18 20:38:29 +00:00
|
|
|
result: Option<Result<Resource, String>>,
|
2021-01-01 16:11:05 +00:00
|
|
|
|
2021-09-29 06:45:13 +00:00
|
|
|
#[cfg_attr(feature = "serde", serde(skip))]
|
2020-11-20 18:50:47 +00:00
|
|
|
tex_mngr: TexMngr,
|
2020-11-17 22:24:14 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 16:11:05 +00:00
|
|
|
impl Default for HttpApp {
|
2020-11-17 22:24:14 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2020-11-17 23:43:58 +00:00
|
|
|
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
2021-08-15 14:56:46 +00:00
|
|
|
method: Method::Get,
|
|
|
|
request_body: r#"["posting some json", { "more_json" : true }]"#.to_owned(),
|
2020-11-18 20:38:29 +00:00
|
|
|
in_progress: Default::default(),
|
|
|
|
result: Default::default(),
|
2020-11-20 18:50:47 +00:00
|
|
|
tex_mngr: Default::default(),
|
2020-11-17 22:24:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-01 16:11:05 +00:00
|
|
|
impl epi::App for HttpApp {
|
2020-12-18 21:30:59 +00:00
|
|
|
fn name(&self) -> &str {
|
2021-01-01 23:13:34 +00:00
|
|
|
"⬇ HTTP"
|
2020-12-18 21:30:59 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 22:24:14 +00:00
|
|
|
/// Called each time the UI needs repainting, which may be many times per second.
|
2021-05-26 20:06:10 +00:00
|
|
|
/// Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`.
|
2021-01-01 19:22:18 +00:00
|
|
|
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
2020-11-20 19:35:16 +00:00
|
|
|
if let Some(receiver) = &mut self.in_progress {
|
|
|
|
// Are we there yet?
|
|
|
|
if let Ok(result) = receiver.try_recv() {
|
|
|
|
self.in_progress = None;
|
|
|
|
self.result = Some(result.map(Resource::from_response));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 22:24:14 +00:00
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
2021-01-02 00:01:01 +00:00
|
|
|
ui.heading("HTTP Fetch Example");
|
2021-09-03 19:04:43 +00:00
|
|
|
ui.horizontal(|ui| {
|
|
|
|
ui.spacing_mut().item_spacing.x = 0.0;
|
|
|
|
ui.label("HTTP requests made using ");
|
|
|
|
ui.hyperlink_to("ehttp", "https://www.github.com/emilk/ehttp");
|
|
|
|
ui.label(".");
|
|
|
|
});
|
2020-11-17 23:43:58 +00:00
|
|
|
ui.add(egui::github_link_file!(
|
|
|
|
"https://github.com/emilk/egui/blob/master/",
|
2021-09-03 19:04:43 +00:00
|
|
|
"(demo source code)"
|
2020-11-17 23:43:58 +00:00
|
|
|
));
|
2020-11-17 22:24:14 +00:00
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
if let Some(request) = ui_url(
|
|
|
|
ui,
|
|
|
|
frame,
|
|
|
|
&mut self.url,
|
|
|
|
&mut self.method,
|
|
|
|
&mut self.request_body,
|
|
|
|
) {
|
2020-12-31 13:31:11 +00:00
|
|
|
let repaint_signal = frame.repaint_signal();
|
2020-11-18 20:38:29 +00:00
|
|
|
let (sender, receiver) = std::sync::mpsc::channel();
|
|
|
|
self.in_progress = Some(receiver);
|
2020-12-30 19:56:50 +00:00
|
|
|
|
2021-09-03 19:04:43 +00:00
|
|
|
ehttp::fetch(request, move |response| {
|
2020-12-30 19:56:50 +00:00
|
|
|
sender.send(response).ok();
|
2020-11-20 19:35:16 +00:00
|
|
|
repaint_signal.request_repaint();
|
2020-11-17 23:43:58 +00:00
|
|
|
});
|
|
|
|
}
|
2020-11-17 22:24:14 +00:00
|
|
|
|
2020-11-18 20:38:29 +00:00
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
if self.in_progress.is_some() {
|
|
|
|
ui.label("Please wait...");
|
|
|
|
} else if let Some(result) = &self.result {
|
2020-11-17 23:43:58 +00:00
|
|
|
match result {
|
2020-11-18 20:38:29 +00:00
|
|
|
Ok(resource) => {
|
2021-02-03 19:04:57 +00:00
|
|
|
ui_resource(ui, frame, &mut self.tex_mngr, resource);
|
2020-11-17 23:43:58 +00:00
|
|
|
}
|
|
|
|
Err(error) => {
|
|
|
|
// This should only happen if the fetch API isn't available or something similar.
|
2021-08-15 14:56:46 +00:00
|
|
|
ui.add(
|
|
|
|
egui::Label::new(if error.is_empty() { "Error" } else { error })
|
|
|
|
.text_color(egui::Color32::RED),
|
|
|
|
);
|
2020-11-17 23:43:58 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-17 22:24:14 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-11-17 23:43:58 +00:00
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
fn ui_url(
|
|
|
|
ui: &mut egui::Ui,
|
|
|
|
frame: &mut epi::Frame<'_>,
|
|
|
|
url: &mut String,
|
|
|
|
method: &mut Method,
|
|
|
|
request_body: &mut String,
|
2021-09-03 19:04:43 +00:00
|
|
|
) -> Option<ehttp::Request> {
|
2020-11-18 20:38:29 +00:00
|
|
|
let mut trigger_fetch = false;
|
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
egui::Grid::new("request_params").show(ui, |ui| {
|
2020-11-18 20:38:29 +00:00
|
|
|
ui.label("URL:");
|
2021-08-15 14:56:46 +00:00
|
|
|
ui.horizontal(|ui| {
|
|
|
|
trigger_fetch |= ui.text_edit_singleline(url).lost_focus();
|
|
|
|
egui::ComboBox::from_id_source("method")
|
|
|
|
.selected_text(format!("{:?}", method))
|
|
|
|
.width(60.0)
|
|
|
|
.show_ui(ui, |ui| {
|
|
|
|
ui.selectable_value(method, Method::Get, "GET");
|
|
|
|
ui.selectable_value(method, Method::Post, "POST");
|
|
|
|
});
|
|
|
|
trigger_fetch |= ui.button("▶").clicked();
|
|
|
|
});
|
|
|
|
ui.end_row();
|
|
|
|
if *method == Method::Post {
|
|
|
|
ui.label("Body:");
|
|
|
|
ui.add(
|
|
|
|
egui::TextEdit::multiline(request_body)
|
|
|
|
.code_editor()
|
|
|
|
.desired_rows(1),
|
|
|
|
);
|
|
|
|
ui.end_row();
|
|
|
|
}
|
2020-11-20 18:50:47 +00:00
|
|
|
});
|
2020-11-18 20:38:29 +00:00
|
|
|
|
2021-01-02 00:01:01 +00:00
|
|
|
if frame.is_web() {
|
|
|
|
ui.label("HINT: paste the url of this page into the field above!");
|
|
|
|
}
|
2020-12-28 18:50:48 +00:00
|
|
|
|
2020-11-20 18:50:47 +00:00
|
|
|
ui.horizontal(|ui| {
|
2021-01-25 17:50:19 +00:00
|
|
|
if ui.button("Source code for this example").clicked() {
|
2020-11-18 20:38:29 +00:00
|
|
|
*url = format!(
|
|
|
|
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
|
|
|
file!()
|
|
|
|
);
|
2021-08-15 14:56:46 +00:00
|
|
|
*method = Method::Get;
|
2020-11-18 20:38:29 +00:00
|
|
|
trigger_fetch = true;
|
|
|
|
}
|
2021-01-25 17:50:19 +00:00
|
|
|
if ui.button("Random image").clicked() {
|
2020-11-18 20:38:29 +00:00
|
|
|
let seed = ui.input().time;
|
|
|
|
let width = 640;
|
|
|
|
let height = 480;
|
|
|
|
*url = format!("https://picsum.photos/seed/{}/{}/{}", seed, width, height);
|
2021-08-15 14:56:46 +00:00
|
|
|
*method = Method::Get;
|
|
|
|
trigger_fetch = true;
|
|
|
|
}
|
|
|
|
if ui.button("Post to httpbin.org").clicked() {
|
|
|
|
*url = "https://httpbin.org/post".to_owned();
|
|
|
|
*method = Method::Post;
|
2020-11-18 20:38:29 +00:00
|
|
|
trigger_fetch = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-12-28 18:50:48 +00:00
|
|
|
if trigger_fetch {
|
2021-08-15 14:56:46 +00:00
|
|
|
Some(match *method {
|
2021-09-03 19:04:43 +00:00
|
|
|
Method::Get => ehttp::Request::get(url),
|
|
|
|
Method::Post => ehttp::Request::post(url, request_body),
|
2021-08-15 14:56:46 +00:00
|
|
|
})
|
2020-12-28 18:50:48 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2020-11-18 20:38:29 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 19:04:57 +00:00
|
|
|
fn ui_resource(
|
2020-11-20 18:50:47 +00:00
|
|
|
ui: &mut egui::Ui,
|
2020-12-31 13:31:11 +00:00
|
|
|
frame: &mut epi::Frame<'_>,
|
2020-11-20 18:50:47 +00:00
|
|
|
tex_mngr: &mut TexMngr,
|
|
|
|
resource: &Resource,
|
|
|
|
) {
|
2020-12-28 18:50:48 +00:00
|
|
|
let Resource {
|
|
|
|
response,
|
2021-08-15 14:56:46 +00:00
|
|
|
text,
|
2020-12-28 18:50:48 +00:00
|
|
|
image,
|
|
|
|
colored_text,
|
|
|
|
} = resource;
|
2020-11-18 20:38:29 +00:00
|
|
|
|
|
|
|
ui.monospace(format!("url: {}", response.url));
|
2020-11-17 23:43:58 +00:00
|
|
|
ui.monospace(format!(
|
2020-11-18 20:38:29 +00:00
|
|
|
"status: {} ({})",
|
2020-11-17 23:43:58 +00:00
|
|
|
response.status, response.status_text
|
|
|
|
));
|
2020-11-18 20:38:29 +00:00
|
|
|
ui.monospace(format!(
|
2021-08-15 14:56:46 +00:00
|
|
|
"content-type: {}",
|
|
|
|
response.content_type().unwrap_or_default()
|
|
|
|
));
|
|
|
|
ui.monospace(format!(
|
|
|
|
"size: {:.1} kB",
|
2020-11-18 20:38:29 +00:00
|
|
|
response.bytes.len() as f32 / 1000.0
|
|
|
|
));
|
2020-11-17 23:43:58 +00:00
|
|
|
|
2021-08-15 14:56:46 +00:00
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
egui::CollapsingHeader::new("Response headers")
|
|
|
|
.default_open(false)
|
|
|
|
.show(ui, |ui| {
|
|
|
|
egui::Grid::new("response_headers")
|
|
|
|
.spacing(egui::vec2(ui.spacing().item_spacing.x * 2.0, 0.0))
|
|
|
|
.show(ui, |ui| {
|
|
|
|
for header in &response.headers {
|
|
|
|
ui.label(header.0);
|
|
|
|
ui.label(header.1);
|
|
|
|
ui.end_row();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
if let Some(text) = &text {
|
2020-12-28 18:50:48 +00:00
|
|
|
let tooltip = "Click to copy the response body";
|
2021-01-25 17:50:19 +00:00
|
|
|
if ui.button("📋").on_hover_text(tooltip).clicked() {
|
2020-12-28 18:50:48 +00:00
|
|
|
ui.output().copied_text = text.clone();
|
2020-11-18 20:38:29 +00:00
|
|
|
}
|
2020-12-28 18:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ui.separator();
|
|
|
|
|
2021-10-07 19:08:01 +00:00
|
|
|
egui::ScrollArea::vertical()
|
|
|
|
.auto_shrink([false; 2])
|
|
|
|
.show(ui, |ui| {
|
|
|
|
if let Some(image) = image {
|
|
|
|
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) {
|
|
|
|
let size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32);
|
|
|
|
ui.image(texture_id, size);
|
|
|
|
}
|
|
|
|
} else if let Some(colored_text) = colored_text {
|
|
|
|
colored_text.ui(ui);
|
|
|
|
} else if let Some(text) = &text {
|
|
|
|
ui.monospace(text);
|
|
|
|
} else {
|
|
|
|
ui.monospace("[binary]");
|
2020-12-28 18:50:48 +00:00
|
|
|
}
|
2021-10-07 19:08:01 +00:00
|
|
|
});
|
2020-12-28 18:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Syntax highlighting:
|
|
|
|
|
2021-01-31 15:52:26 +00:00
|
|
|
#[cfg(feature = "syntect")]
|
2021-09-03 19:04:43 +00:00
|
|
|
fn syntax_highlighting(response: &ehttp::Response, text: &str) -> Option<ColoredText> {
|
2020-12-28 18:50:48 +00:00
|
|
|
let extension_and_rest: Vec<&str> = response.url.rsplitn(2, '.').collect();
|
|
|
|
let extension = extension_and_rest.get(0)?;
|
|
|
|
ColoredText::text_with_extension(text, extension)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Lines of text fragments
|
2021-01-31 15:52:26 +00:00
|
|
|
#[cfg(feature = "syntect")]
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
struct ColoredText(egui::text::LayoutJob);
|
2020-12-28 18:50:48 +00:00
|
|
|
|
2021-01-31 15:52:26 +00:00
|
|
|
#[cfg(feature = "syntect")]
|
2020-12-28 18:50:48 +00:00
|
|
|
impl ColoredText {
|
2021-01-01 16:11:05 +00:00
|
|
|
/// e.g. `text_with_extension("fn foo() {}", "rs")`
|
2020-12-28 18:50:48 +00:00
|
|
|
pub fn text_with_extension(text: &str, extension: &str) -> Option<ColoredText> {
|
|
|
|
use syntect::easy::HighlightLines;
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
use syntect::highlighting::FontStyle;
|
2020-12-28 18:50:48 +00:00
|
|
|
use syntect::highlighting::ThemeSet;
|
|
|
|
use syntect::parsing::SyntaxSet;
|
|
|
|
use syntect::util::LinesWithEndings;
|
|
|
|
|
|
|
|
let ps = SyntaxSet::load_defaults_newlines(); // should be cached and reused
|
|
|
|
let ts = ThemeSet::load_defaults(); // should be cached and reused
|
|
|
|
|
|
|
|
let syntax = ps.find_syntax_by_extension(extension)?;
|
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
let dark_mode = true;
|
|
|
|
let theme = if dark_mode {
|
|
|
|
"base16-mocha.dark"
|
|
|
|
} else {
|
|
|
|
"base16-ocean.light"
|
|
|
|
};
|
|
|
|
let mut h = HighlightLines::new(syntax, &ts.themes[theme]);
|
|
|
|
|
|
|
|
use egui::text::{LayoutJob, LayoutSection, TextFormat};
|
2020-12-28 18:50:48 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
let mut job = LayoutJob {
|
|
|
|
text: text.into(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
for line in LinesWithEndings::from(text) {
|
|
|
|
for (style, range) in h.highlight(line, &ps) {
|
|
|
|
let fg = style.foreground;
|
|
|
|
let text_color = egui::Color32::from_rgb(fg.r, fg.g, fg.b);
|
|
|
|
let italics = style.font_style.contains(FontStyle::ITALIC);
|
|
|
|
let underline = style.font_style.contains(FontStyle::ITALIC);
|
|
|
|
let underline = if underline {
|
|
|
|
egui::Stroke::new(1.0, text_color)
|
|
|
|
} else {
|
|
|
|
egui::Stroke::none()
|
|
|
|
};
|
|
|
|
job.sections.push(LayoutSection {
|
|
|
|
leading_space: 0.0,
|
|
|
|
byte_range: as_byte_range(text, range),
|
|
|
|
format: TextFormat {
|
|
|
|
style: egui::TextStyle::Monospace,
|
|
|
|
color: text_color,
|
|
|
|
italics,
|
|
|
|
underline,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-12-28 18:50:48 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
Some(ColoredText(job))
|
2020-12-28 18:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ui(&self, ui: &mut egui::Ui) {
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
let mut job = self.0.clone();
|
|
|
|
job.wrap_width = ui.available_width();
|
|
|
|
let galley = ui.fonts().layout_job(job);
|
2021-09-07 18:37:50 +00:00
|
|
|
let (response, painter) = ui.allocate_painter(galley.size(), egui::Sense::hover());
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
painter.add(egui::Shape::galley(response.rect.min, galley));
|
2020-11-18 20:38:29 +00:00
|
|
|
}
|
2020-11-17 23:43:58 +00:00
|
|
|
}
|
2020-11-20 18:50:47 +00:00
|
|
|
|
2021-09-03 19:04:43 +00:00
|
|
|
#[cfg(feature = "syntect")]
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
fn as_byte_range(whole: &str, range: &str) -> std::ops::Range<usize> {
|
|
|
|
let whole_start = whole.as_ptr() as usize;
|
|
|
|
let range_start = range.as_ptr() as usize;
|
|
|
|
assert!(whole_start <= range_start);
|
|
|
|
assert!(range_start + range.len() <= whole_start + whole.len());
|
|
|
|
let offset = range_start - whole_start;
|
|
|
|
offset..(offset + range.len())
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:52:26 +00:00
|
|
|
#[cfg(not(feature = "syntect"))]
|
2021-09-03 19:04:43 +00:00
|
|
|
fn syntax_highlighting(_: &ehttp::Response, _: &str) -> Option<ColoredText> {
|
2021-01-31 15:52:26 +00:00
|
|
|
None
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "syntect"))]
|
|
|
|
struct ColoredText();
|
|
|
|
#[cfg(not(feature = "syntect"))]
|
|
|
|
impl ColoredText {
|
|
|
|
pub fn ui(&self, _ui: &mut egui::Ui) {}
|
|
|
|
}
|
|
|
|
|
2020-11-20 18:50:47 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Texture/image handling is very manual at the moment.
|
|
|
|
|
2020-12-28 18:50:48 +00:00
|
|
|
/// Immediate mode texture manager that supports at most one texture at the time :)
|
2020-11-20 18:50:47 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
struct TexMngr {
|
|
|
|
loaded_url: String,
|
|
|
|
texture_id: Option<egui::TextureId>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TexMngr {
|
|
|
|
fn texture(
|
|
|
|
&mut self,
|
2020-12-31 13:31:11 +00:00
|
|
|
frame: &mut epi::Frame<'_>,
|
2020-11-20 18:50:47 +00:00
|
|
|
url: &str,
|
|
|
|
image: &Image,
|
|
|
|
) -> Option<egui::TextureId> {
|
|
|
|
if self.loaded_url != url {
|
2021-01-07 15:29:58 +00:00
|
|
|
if let Some(texture_id) = self.texture_id.take() {
|
2021-02-28 18:09:48 +00:00
|
|
|
frame.tex_allocator().free(texture_id);
|
2021-01-07 15:29:58 +00:00
|
|
|
}
|
|
|
|
|
2021-02-28 18:09:48 +00:00
|
|
|
self.texture_id = Some(
|
|
|
|
frame
|
|
|
|
.tex_allocator()
|
|
|
|
.alloc_srgba_premultiplied(image.size, &image.pixels),
|
|
|
|
);
|
2020-11-20 18:50:47 +00:00
|
|
|
self.loaded_url = url.to_owned();
|
|
|
|
}
|
2021-01-07 15:29:58 +00:00
|
|
|
self.texture_id
|
2020-11-20 18:50:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Image {
|
|
|
|
size: (usize, usize),
|
2021-01-02 16:02:18 +00:00
|
|
|
pixels: Vec<egui::Color32>,
|
2020-11-20 18:50:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Image {
|
|
|
|
fn decode(bytes: &[u8]) -> Option<Image> {
|
|
|
|
use image::GenericImageView;
|
2021-01-01 16:11:05 +00:00
|
|
|
let image = image::load_from_memory(bytes).ok()?;
|
2020-11-28 10:06:39 +00:00
|
|
|
let image_buffer = image.to_rgba8();
|
2020-11-20 18:50:47 +00:00
|
|
|
let size = (image.width() as usize, image.height() as usize);
|
|
|
|
let pixels = image_buffer.into_vec();
|
|
|
|
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
|
|
|
let pixels = pixels
|
|
|
|
.chunks(4)
|
2021-01-02 16:02:18 +00:00
|
|
|
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
2020-11-20 18:50:47 +00:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
Some(Image { size, pixels })
|
|
|
|
}
|
|
|
|
}
|