[example_web] show loading of an image
Required some redesign of `TextureAllocator` as well as some improvements to the fetch API.
This commit is contained in:
parent
90cecace0c
commit
c6ce0b9e8c
16 changed files with 645 additions and 277 deletions
108
Cargo.lock
generated
108
Cargo.lock
generated
|
@ -21,6 +21,12 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler32"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -72,7 +78,7 @@ dependencies = [
|
||||||
"addr2line",
|
"addr2line",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.4.3",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
@ -107,6 +113,12 @@ version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
@ -245,6 +257,12 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_fn"
|
name = "const_fn"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -333,6 +351,15 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "criterion"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -473,6 +500,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deflate"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "demo_glium"
|
name = "demo_glium"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -586,6 +623,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"egui",
|
"egui",
|
||||||
"egui_web",
|
"egui_web",
|
||||||
|
"image",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -755,6 +793,22 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.23.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4f0a8345b33b082aedec2f4d7d4a926b845cee184cbe78b703413066564431b"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"jpeg-decoder",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -794,6 +848,15 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jpeg-decoder"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.45"
|
version = "0.3.45"
|
||||||
|
@ -905,6 +968,15 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -1046,6 +1118,28 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -1202,6 +1296,18 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.16.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"deflate",
|
||||||
|
"miniz_oxide 0.3.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
2
TODO.md
2
TODO.md
|
@ -4,7 +4,7 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
||||||
|
|
||||||
## Top priority
|
## Top priority
|
||||||
|
|
||||||
* Egui-web fetch
|
* Egui-web local storage
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Egui – An experimental immediate mode GUI written in Rust</title>
|
<title>Egui Example App</title>
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
/* Remove touch delay: */
|
/* Remove touch delay: */
|
||||||
|
|
|
@ -417,6 +417,13 @@ async function init(input) {
|
||||||
var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2));
|
var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_get_d015313eb9359d3a = handleError(function(arg0, arg1, arg2, arg3) {
|
||||||
|
var ret = getObject(arg1).get(getStringFromWasm0(arg2, arg3));
|
||||||
|
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
});
|
||||||
imports.wbg.__wbg_set_e0c72ee4d5eea3d5 = handleError(function(arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_set_e0c72ee4d5eea3d5 = handleError(function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||||
});
|
});
|
||||||
|
@ -454,8 +461,12 @@ async function init(input) {
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_text_966d07536ca6ccdc = handleError(function(arg0) {
|
imports.wbg.__wbg_headers_c736e1fe38752cff = function(arg0) {
|
||||||
var ret = getObject(arg0).text();
|
var ret = getObject(arg0).headers;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_arrayBuffer_dc33ab7b8cdf0d63 = handleError(function(arg0) {
|
||||||
|
var ret = getObject(arg0).arrayBuffer();
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
});
|
});
|
||||||
imports.wbg.__wbg_now_49847177a6d1d57e = function(arg0) {
|
imports.wbg.__wbg_now_49847177a6d1d57e = function(arg0) {
|
||||||
|
@ -672,6 +683,9 @@ async function init(input) {
|
||||||
imports.wbg.__wbg_log_3bafd82835c6de6d = function(arg0) {
|
imports.wbg.__wbg_log_3bafd82835c6de6d = function(arg0) {
|
||||||
console.log(getObject(arg0));
|
console.log(getObject(arg0));
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_warn_d05e82888b7fad05 = function(arg0) {
|
||||||
|
console.warn(getObject(arg0));
|
||||||
|
};
|
||||||
imports.wbg.__wbg_style_9a41d46c005f7596 = function(arg0) {
|
imports.wbg.__wbg_style_9a41d46c005f7596 = function(arg0) {
|
||||||
var ret = getObject(arg0).style;
|
var ret = getObject(arg0).style;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
@ -789,6 +803,13 @@ async function init(input) {
|
||||||
var ret = new Uint8Array(getObject(arg0));
|
var ret = new Uint8Array(getObject(arg0));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_set_3bb960a9975f3cd2 = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_length_2b13641a9d906653 = function(arg0) {
|
||||||
|
var ret = getObject(arg0).length;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
imports.wbg.__wbg_new_79f4487112eba5a7 = function(arg0) {
|
imports.wbg.__wbg_new_79f4487112eba5a7 = function(arg0) {
|
||||||
var ret = new Float32Array(getObject(arg0));
|
var ret = new Float32Array(getObject(arg0));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
@ -849,36 +870,36 @@ async function init(input) {
|
||||||
var ret = wasm.memory;
|
var ret = wasm.memory;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper472 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper738 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_26);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_26);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper473 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper739 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_29);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_29);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper475 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper741 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_32);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_32);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper477 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper743 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_35);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_35);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper479 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper745 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_38);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_38);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper482 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper748 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_41);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_41);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper484 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper750 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_44);
|
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_44);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper1212 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper1475 = function(arg0, arg1, arg2) {
|
||||||
var ret = makeMutClosure(arg0, arg1, 132, __wbg_adapter_47);
|
var ret = makeMutClosure(arg0, arg1, 242, __wbg_adapter_47);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -77,12 +77,19 @@ pub struct AppOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TextureAllocator {
|
pub trait TextureAllocator {
|
||||||
/// Allocate a user texture (EXPERIMENTAL!)
|
/// A.locate a new user texture.
|
||||||
fn new_texture_srgba_premultiplied(
|
fn alloc(&mut self) -> crate::TextureId;
|
||||||
|
|
||||||
|
/// Set or change the pixels of a user texture.
|
||||||
|
fn set_srgba_premultiplied(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
id: crate::TextureId,
|
||||||
size: (usize, usize),
|
size: (usize, usize),
|
||||||
pixels: &[crate::Srgba],
|
srgba_pixels: &[crate::Srgba],
|
||||||
) -> crate::TextureId;
|
);
|
||||||
|
|
||||||
|
/// Free the given texture.
|
||||||
|
fn free(&mut self, id: crate::TextureId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||||
|
|
|
@ -41,7 +41,7 @@ impl ColorTest {
|
||||||
ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500");
|
ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500");
|
||||||
ui.wrap(|ui| {
|
ui.wrap(|ui| {
|
||||||
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
|
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
|
||||||
let g = Gradient::one_color(Srgba::new(255, 165, 0, 255));
|
let g = Gradient::one_color(Srgba::from_rgb(255, 165, 0));
|
||||||
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
|
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
|
||||||
self.tex_gradient(
|
self.tex_gradient(
|
||||||
ui,
|
ui,
|
||||||
|
@ -125,7 +125,7 @@ impl ColorTest {
|
||||||
ui,
|
ui,
|
||||||
tex_allocator,
|
tex_allocator,
|
||||||
RED,
|
RED,
|
||||||
(TRANSPARENT, Srgba::new(0, 0, 255, 0)),
|
(TRANSPARENT, Srgba::from_rgba_premultiplied(0, 0, 255, 0)),
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -367,7 +367,9 @@ impl TextureManager {
|
||||||
let pixels = gradient.to_pixel_row();
|
let pixels = gradient.to_pixel_row();
|
||||||
let width = pixels.len();
|
let width = pixels.len();
|
||||||
let height = 1;
|
let height = 1;
|
||||||
tex_allocator.new_texture_srgba_premultiplied((width, height), &pixels)
|
let id = tex_allocator.alloc();
|
||||||
|
tex_allocator.set_srgba_premultiplied(id, (width, height), &pixels);
|
||||||
|
id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,41 @@ impl std::ops::IndexMut<usize> for Srgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove ?
|
||||||
pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Srgba {
|
pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Srgba {
|
||||||
Srgba::new(r, g, b, a)
|
Srgba::from_rgba_premultiplied(r, g, b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Srgba {
|
impl Srgba {
|
||||||
|
#[deprecated = "Use from_rgb(..), from_rgba_premultiplied(..) or from_srgba_unmultiplied(..)"]
|
||||||
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
Self([r, g, b, a])
|
Self([r, g, b, a])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||||
|
Self([r, g, b, 255])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
|
||||||
|
Self([r, g, b, 0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// From `sRGBA` with premultiplied alpha.
|
||||||
|
pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
|
Self([r, g, b, a])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// From `sRGBA` WITHOUT premultiplied alpha.
|
||||||
|
pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
|
if a == 255 {
|
||||||
|
Self::from_rgba_premultiplied(r, g, b, a) // common-case optimization
|
||||||
|
} else {
|
||||||
|
Rgba::from(Self::from_rgb(r, g, b))
|
||||||
|
.multiply(a as f32 / 255.0)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn gray(l: u8) -> Self {
|
pub const fn gray(l: u8) -> Self {
|
||||||
Self([l, l, l, 255])
|
Self([l, l, l, 255])
|
||||||
}
|
}
|
||||||
|
@ -476,7 +502,7 @@ fn test_hsv_roundtrip() {
|
||||||
for r in 0..=255 {
|
for r in 0..=255 {
|
||||||
for g in 0..=255 {
|
for g in 0..=255 {
|
||||||
for b in 0..=255 {
|
for b in 0..=255 {
|
||||||
let srgba = Srgba::new(r, g, b, 255);
|
let srgba = Srgba::from_rgb(r, g, b);
|
||||||
let hsva = Hsva::from(srgba);
|
let hsva = Hsva::from(srgba);
|
||||||
assert_eq!(srgba, Srgba::from(hsva));
|
assert_eq!(srgba, Srgba::from(hsva));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,21 @@ const EGUI_MEMORY_KEY: &str = "egui";
|
||||||
const WINDOW_KEY: &str = "window";
|
const WINDOW_KEY: &str = "window";
|
||||||
|
|
||||||
impl egui::app::TextureAllocator for Painter {
|
impl egui::app::TextureAllocator for Painter {
|
||||||
fn new_texture_srgba_premultiplied(
|
fn alloc(&mut self) -> egui::TextureId {
|
||||||
|
self.alloc_user_texture()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_srgba_premultiplied(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
id: egui::TextureId,
|
||||||
size: (usize, usize),
|
size: (usize, usize),
|
||||||
pixels: &[Srgba],
|
srgba_pixels: &[Srgba],
|
||||||
) -> egui::TextureId {
|
) {
|
||||||
self.new_user_texture(size, pixels)
|
self.set_user_texture(id, size, srgba_pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&mut self, id: egui::TextureId) {
|
||||||
|
self.free_user_texture(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,8 @@ pub struct Painter {
|
||||||
egui_texture: Option<SrgbTexture2d>,
|
egui_texture: Option<SrgbTexture2d>,
|
||||||
egui_texture_version: Option<u64>,
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
user_textures: Vec<UserTexture>,
|
/// `None` means unallocated (freed) slot.
|
||||||
|
user_textures: Vec<Option<UserTexture>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -77,7 +78,7 @@ struct UserTexture {
|
||||||
pixels: Vec<Vec<(u8, u8, u8, u8)>>,
|
pixels: Vec<Vec<(u8, u8, u8, u8)>>,
|
||||||
|
|
||||||
/// Lazily uploaded
|
/// Lazily uploaded
|
||||||
texture: Option<SrgbTexture2d>,
|
gl_texture: Option<SrgbTexture2d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
@ -94,20 +95,62 @@ impl Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_user_texture(&mut self, size: (usize, usize), pixels: &[Srgba]) -> egui::TextureId {
|
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||||
|
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||||
|
if tex.is_none() {
|
||||||
|
*tex = Some(Default::default());
|
||||||
|
return egui::TextureId::User(i as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||||
|
self.user_textures.push(Some(Default::default()));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_user_texture(
|
||||||
|
&mut self,
|
||||||
|
id: egui::TextureId,
|
||||||
|
size: (usize, usize),
|
||||||
|
pixels: &[Srgba],
|
||||||
|
) {
|
||||||
assert_eq!(size.0 * size.1, pixels.len());
|
assert_eq!(size.0 * size.1, pixels.len());
|
||||||
|
|
||||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
if let egui::TextureId::User(id) = id {
|
||||||
.chunks(size.0 as usize)
|
if let Some(user_texture) = self.user_textures.get_mut(id as usize) {
|
||||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
if let Some(user_texture) = user_texture {
|
||||||
.collect();
|
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
||||||
|
.chunks(size.0 as usize)
|
||||||
|
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
*user_texture = UserTexture {
|
||||||
self.user_textures.push(UserTexture {
|
pixels,
|
||||||
pixels,
|
gl_texture: None,
|
||||||
texture: None,
|
};
|
||||||
});
|
}
|
||||||
id
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||||
|
if let egui::TextureId::User(id) = id {
|
||||||
|
let index = id as usize;
|
||||||
|
if index < self.user_textures.len() {
|
||||||
|
self.user_textures[index] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||||
|
match texture_id {
|
||||||
|
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||||
|
egui::TextureId::User(id) => self
|
||||||
|
.user_textures
|
||||||
|
.get(id as usize)?
|
||||||
|
.as_ref()?
|
||||||
|
.gl_texture
|
||||||
|
.as_ref(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_egui_texture(
|
fn upload_egui_texture(
|
||||||
|
@ -138,12 +181,14 @@ impl Painter {
|
||||||
|
|
||||||
fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
||||||
for user_texture in &mut self.user_textures {
|
for user_texture in &mut self.user_textures {
|
||||||
if user_texture.texture.is_none() {
|
if let Some(user_texture) = user_texture {
|
||||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
if user_texture.gl_texture.is_none() {
|
||||||
let format = texture::SrgbFormat::U8U8U8U8;
|
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
let format = texture::SrgbFormat::U8U8U8U8;
|
||||||
user_texture.texture =
|
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||||
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
user_texture.gl_texture =
|
||||||
|
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,18 +218,6 @@ impl Painter {
|
||||||
target.finish().unwrap();
|
target.finish().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_texture(&self, texture_id: egui::TextureId) -> &SrgbTexture2d {
|
|
||||||
match texture_id {
|
|
||||||
egui::TextureId::Egui => self.egui_texture.as_ref().unwrap(),
|
|
||||||
egui::TextureId::User(id) => {
|
|
||||||
let id = id as usize;
|
|
||||||
assert!(id < self.user_textures.len());
|
|
||||||
let texture = self.user_textures[id].texture.as_ref();
|
|
||||||
texture.expect("Should have been uploaded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(never)] // Easier profiling
|
#[inline(never)] // Easier profiling
|
||||||
fn paint_job(
|
fn paint_job(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -229,68 +262,68 @@ impl Painter {
|
||||||
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
||||||
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
||||||
|
|
||||||
let texture = self.get_texture(triangles.texture_id);
|
if let Some(texture) = self.get_texture(triangles.texture_id) {
|
||||||
|
let uniforms = uniform! {
|
||||||
|
u_screen_size: [width_in_points, height_in_points],
|
||||||
|
u_sampler: texture.sampled().wrap_function(SamplerWrapFunction::Clamp),
|
||||||
|
};
|
||||||
|
|
||||||
let uniforms = uniform! {
|
// Egui outputs colors with premultiplied alpha:
|
||||||
u_screen_size: [width_in_points, height_in_points],
|
let color_blend_func = glium::BlendingFunction::Addition {
|
||||||
u_sampler: texture.sampled().wrap_function(SamplerWrapFunction::Clamp),
|
source: glium::LinearBlendingFactor::One,
|
||||||
};
|
destination: glium::LinearBlendingFactor::OneMinusSourceAlpha,
|
||||||
|
};
|
||||||
|
|
||||||
// Egui outputs colors with premultiplied alpha:
|
// Less important, but this is technically the correct alpha blend function
|
||||||
let color_blend_func = glium::BlendingFunction::Addition {
|
// when you want to make use of the framebuffer alpha (for screenshots, compositing, etc).
|
||||||
source: glium::LinearBlendingFactor::One,
|
let alpha_blend_func = glium::BlendingFunction::Addition {
|
||||||
destination: glium::LinearBlendingFactor::OneMinusSourceAlpha,
|
source: glium::LinearBlendingFactor::OneMinusDestinationAlpha,
|
||||||
};
|
destination: glium::LinearBlendingFactor::One,
|
||||||
|
};
|
||||||
|
|
||||||
// Less important, but this is technically the correct alpha blend function
|
let blend = glium::Blend {
|
||||||
// when you want to make use of the framebuffer alpha (for screenshots, compositing, etc).
|
color: color_blend_func,
|
||||||
let alpha_blend_func = glium::BlendingFunction::Addition {
|
alpha: alpha_blend_func,
|
||||||
source: glium::LinearBlendingFactor::OneMinusDestinationAlpha,
|
..Default::default()
|
||||||
destination: glium::LinearBlendingFactor::One,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let blend = glium::Blend {
|
// Transform clip rect to physical pixels:
|
||||||
color: color_blend_func,
|
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||||
alpha: alpha_blend_func,
|
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||||
..Default::default()
|
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||||
};
|
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||||
|
|
||||||
// Transform clip rect to physical pixels:
|
// Make sure clip rect can fit withing an `u32`:
|
||||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
let clip_min_x = clamp(clip_min_x, 0.0..=width_in_pixels as f32);
|
||||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
let clip_min_y = clamp(clip_min_y, 0.0..=height_in_pixels as f32);
|
||||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
let clip_max_x = clamp(clip_max_x, clip_min_x..=width_in_pixels as f32);
|
||||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
let clip_max_y = clamp(clip_max_y, clip_min_y..=height_in_pixels as f32);
|
||||||
|
|
||||||
// Make sure clip rect can fit withing an `u32`:
|
let clip_min_x = clip_min_x.round() as u32;
|
||||||
let clip_min_x = clamp(clip_min_x, 0.0..=width_in_pixels as f32);
|
let clip_min_y = clip_min_y.round() as u32;
|
||||||
let clip_min_y = clamp(clip_min_y, 0.0..=height_in_pixels as f32);
|
let clip_max_x = clip_max_x.round() as u32;
|
||||||
let clip_max_x = clamp(clip_max_x, clip_min_x..=width_in_pixels as f32);
|
let clip_max_y = clip_max_y.round() as u32;
|
||||||
let clip_max_y = clamp(clip_max_y, clip_min_y..=height_in_pixels as f32);
|
|
||||||
|
|
||||||
let clip_min_x = clip_min_x.round() as u32;
|
let params = glium::DrawParameters {
|
||||||
let clip_min_y = clip_min_y.round() as u32;
|
blend,
|
||||||
let clip_max_x = clip_max_x.round() as u32;
|
scissor: Some(glium::Rect {
|
||||||
let clip_max_y = clip_max_y.round() as u32;
|
left: clip_min_x,
|
||||||
|
bottom: height_in_pixels - clip_max_y,
|
||||||
|
width: clip_max_x - clip_min_x,
|
||||||
|
height: clip_max_y - clip_min_y,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let params = glium::DrawParameters {
|
target
|
||||||
blend,
|
.draw(
|
||||||
scissor: Some(glium::Rect {
|
&vertex_buffer,
|
||||||
left: clip_min_x,
|
&index_buffer,
|
||||||
bottom: height_in_pixels - clip_max_y,
|
&self.program,
|
||||||
width: clip_max_x - clip_min_x,
|
&uniforms,
|
||||||
height: clip_max_y - clip_min_y,
|
¶ms,
|
||||||
}),
|
)
|
||||||
..Default::default()
|
.unwrap();
|
||||||
};
|
}
|
||||||
|
|
||||||
target
|
|
||||||
.draw(
|
|
||||||
&vertex_buffer,
|
|
||||||
&index_buffer,
|
|
||||||
&self.program,
|
|
||||||
&uniforms,
|
|
||||||
¶ms,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,12 +81,21 @@ impl WebBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl egui::app::TextureAllocator for webgl::Painter {
|
impl egui::app::TextureAllocator for webgl::Painter {
|
||||||
fn new_texture_srgba_premultiplied(
|
fn alloc(&mut self) -> egui::TextureId {
|
||||||
|
self.alloc_user_texture()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_srgba_premultiplied(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
id: egui::TextureId,
|
||||||
size: (usize, usize),
|
size: (usize, usize),
|
||||||
pixels: &[Srgba],
|
srgba_pixels: &[Srgba],
|
||||||
) -> egui::TextureId {
|
) {
|
||||||
self.new_user_texture(size, pixels)
|
self.set_user_texture(id, size, srgba_pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&mut self, id: egui::TextureId) {
|
||||||
|
self.free_user_texture(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,48 +6,80 @@ pub struct Response {
|
||||||
pub status: u16,
|
pub status: u16,
|
||||||
pub status_text: String,
|
pub status_text: String,
|
||||||
|
|
||||||
pub body: String,
|
/// Content-Type header, or empty string if missing
|
||||||
|
pub header_content_type: String,
|
||||||
|
|
||||||
|
/// The raw bytes
|
||||||
|
pub bytes: Vec<u8>,
|
||||||
|
|
||||||
|
/// UTF-8 decoded version of bytes.
|
||||||
|
/// ONLY if `header_content_type` starts with "text" and bytes is UTF-8.
|
||||||
|
pub text: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NOTE: Ok(..) is returned on network error.
|
/// NOTE: Ok(..) is returned on network error.
|
||||||
/// Err is only for failure to use the fetch api.
|
/// Err is only for failure to use the fetch api.
|
||||||
pub async fn get_text(url: &str) -> Result<Response, String> {
|
pub async fn fetch(method: &str, url: &str) -> Result<Response, String> {
|
||||||
get_text_jsvalue(url)
|
fetch_jsvalue(method, url)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| err.as_string().unwrap_or_default())
|
.map_err(|err| err.as_string().unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NOTE: Ok(..) is returned on network error.
|
/// NOTE: Ok(..) is returned on network error.
|
||||||
/// Err is only for failure to use the fetch api.
|
/// Err is only for failure to use the fetch api.
|
||||||
async fn get_text_jsvalue(url: &str) -> Result<Response, JsValue> {
|
pub async fn get(url: &str) -> Result<Response, String> {
|
||||||
|
fetch("GET", url).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: Ok(..) is returned on network error.
|
||||||
|
/// Err is only for failure to use the fetch api.
|
||||||
|
async fn fetch_jsvalue(method: &str, url: &str) -> Result<Response, JsValue> {
|
||||||
// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
|
// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
|
||||||
|
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
|
|
||||||
let mut opts = web_sys::RequestInit::new();
|
let mut opts = web_sys::RequestInit::new();
|
||||||
opts.method("GET");
|
opts.method(method);
|
||||||
opts.mode(web_sys::RequestMode::Cors);
|
opts.mode(web_sys::RequestMode::Cors);
|
||||||
|
|
||||||
let request = web_sys::Request::new_with_str_and_init(&url, &opts)?;
|
let request = web_sys::Request::new_with_str_and_init(&url, &opts)?;
|
||||||
request.headers().set("Accept", "*/*")?;
|
request.headers().set("Accept", "*/*")?;
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
let response = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||||
|
assert!(response.is_instance_of::<web_sys::Response>());
|
||||||
|
let response: web_sys::Response = response.dyn_into().unwrap();
|
||||||
|
|
||||||
assert!(resp_value.is_instance_of::<web_sys::Response>());
|
// // TODO: support binary get
|
||||||
let resp: web_sys::Response = resp_value.dyn_into().unwrap();
|
|
||||||
|
|
||||||
// TODO: headers
|
// let body = JsFuture::from(response.text()?).await?;
|
||||||
// TODO: support binary get
|
// let body = body.as_string().unwrap_or_default();
|
||||||
let body = JsFuture::from(resp.text()?).await?;
|
|
||||||
let body = body.as_string().unwrap_or_default();
|
let array_buffer = JsFuture::from(response.array_buffer()?).await?;
|
||||||
|
let uint8_array = js_sys::Uint8Array::new(&array_buffer);
|
||||||
|
let bytes = uint8_array.to_vec();
|
||||||
|
|
||||||
|
let header_content_type = response
|
||||||
|
.headers()
|
||||||
|
.get("Content-Type")
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let text = if header_content_type.starts_with("text") {
|
||||||
|
String::from_utf8(bytes.clone()).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Response {
|
Ok(Response {
|
||||||
status_text: resp.status_text(),
|
status_text: response.status_text(),
|
||||||
url: resp.url(),
|
url: response.url(),
|
||||||
ok: resp.ok(),
|
ok: response.ok(),
|
||||||
status: resp.status(),
|
status: response.status(),
|
||||||
body,
|
header_content_type,
|
||||||
|
bytes,
|
||||||
|
text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ pub fn console_log(s: impl Into<JsValue>) {
|
||||||
web_sys::console::log_1(&s.into());
|
web_sys::console::log_1(&s.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn console_warn(s: impl Into<JsValue>) {
|
||||||
|
web_sys::console::warn_1(&s.into());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn console_error(s: impl Into<JsValue>) {
|
pub fn console_error(s: impl Into<JsValue>) {
|
||||||
web_sys::console::error_1(&s.into());
|
web_sys::console::error_1(&s.into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,8 @@ pub struct Painter {
|
||||||
egui_texture: WebGlTexture,
|
egui_texture: WebGlTexture,
|
||||||
egui_texture_version: Option<u64>,
|
egui_texture_version: Option<u64>,
|
||||||
|
|
||||||
user_textures: Vec<UserTexture>,
|
/// `None` means unallocated (freed) slot.
|
||||||
|
user_textures: Vec<Option<UserTexture>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -104,7 +105,7 @@ struct UserTexture {
|
||||||
pixels: Vec<u8>,
|
pixels: Vec<u8>,
|
||||||
|
|
||||||
/// Lazily uploaded
|
/// Lazily uploaded
|
||||||
texture: Option<WebGlTexture>,
|
gl_texture: Option<WebGlTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
@ -165,28 +166,66 @@ impl Painter {
|
||||||
&self.canvas_id
|
&self.canvas_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_user_texture(
|
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||||
|
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||||
|
if tex.is_none() {
|
||||||
|
*tex = Some(Default::default());
|
||||||
|
return egui::TextureId::User(i as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||||
|
self.user_textures.push(Some(Default::default()));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_user_texture(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
id: egui::TextureId,
|
||||||
size: (usize, usize),
|
size: (usize, usize),
|
||||||
srgba_pixels: &[Srgba],
|
srgba_pixels: &[Srgba],
|
||||||
) -> egui::TextureId {
|
) {
|
||||||
assert_eq!(size.0 * size.1, srgba_pixels.len());
|
assert_eq!(size.0 * size.1, srgba_pixels.len());
|
||||||
|
|
||||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
if let egui::TextureId::User(id) = id {
|
||||||
for srgba in srgba_pixels {
|
if let Some(user_texture) = self.user_textures.get_mut(id as usize) {
|
||||||
pixels.push(srgba.r());
|
if let Some(user_texture) = user_texture {
|
||||||
pixels.push(srgba.g());
|
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||||
pixels.push(srgba.b());
|
for srgba in srgba_pixels {
|
||||||
pixels.push(srgba.a());
|
pixels.push(srgba.r());
|
||||||
}
|
pixels.push(srgba.g());
|
||||||
|
pixels.push(srgba.b());
|
||||||
|
pixels.push(srgba.a());
|
||||||
|
}
|
||||||
|
|
||||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
*user_texture = UserTexture {
|
||||||
self.user_textures.push(UserTexture {
|
size,
|
||||||
size,
|
pixels,
|
||||||
pixels,
|
gl_texture: None,
|
||||||
texture: None,
|
};
|
||||||
});
|
}
|
||||||
id
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||||
|
if let egui::TextureId::User(id) = id {
|
||||||
|
let index = id as usize;
|
||||||
|
if index < self.user_textures.len() {
|
||||||
|
self.user_textures[index] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||||
|
match texture_id {
|
||||||
|
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||||
|
egui::TextureId::User(id) => self
|
||||||
|
.user_textures
|
||||||
|
.get(id as usize)?
|
||||||
|
.as_ref()?
|
||||||
|
.gl_texture
|
||||||
|
.as_ref(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_egui_texture(&mut self, texture: &Texture) {
|
fn upload_egui_texture(&mut self, texture: &Texture) {
|
||||||
|
@ -233,50 +272,40 @@ impl Painter {
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
|
|
||||||
for user_texture in &mut self.user_textures {
|
for user_texture in &mut self.user_textures {
|
||||||
if user_texture.texture.is_none() {
|
if let Some(user_texture) = user_texture {
|
||||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
if user_texture.gl_texture.is_none() {
|
||||||
|
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||||
|
|
||||||
let gl_texture = gl.create_texture().unwrap();
|
let gl_texture = gl.create_texture().unwrap();
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||||
|
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||||
|
|
||||||
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
||||||
let level = 0;
|
let level = 0;
|
||||||
let internal_format = Gl::RGBA;
|
let internal_format = Gl::RGBA;
|
||||||
let border = 0;
|
let border = 0;
|
||||||
let src_format = Gl::RGBA;
|
let src_format = Gl::RGBA;
|
||||||
let src_type = Gl::UNSIGNED_BYTE;
|
let src_type = Gl::UNSIGNED_BYTE;
|
||||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||||
Gl::TEXTURE_2D,
|
Gl::TEXTURE_2D,
|
||||||
level,
|
level,
|
||||||
internal_format as i32,
|
internal_format as i32,
|
||||||
user_texture.size.0 as i32,
|
user_texture.size.0 as i32,
|
||||||
user_texture.size.1 as i32,
|
user_texture.size.1 as i32,
|
||||||
border,
|
border,
|
||||||
src_format,
|
src_format,
|
||||||
src_type,
|
src_type,
|
||||||
Some(&pixels),
|
Some(&pixels),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
user_texture.texture = Some(gl_texture);
|
user_texture.gl_texture = Some(gl_texture);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_texture(&self, texture_id: egui::TextureId) -> &WebGlTexture {
|
|
||||||
match texture_id {
|
|
||||||
egui::TextureId::Egui => &self.egui_texture,
|
|
||||||
egui::TextureId::User(id) => {
|
|
||||||
let id = id as usize;
|
|
||||||
assert!(id < self.user_textures.len());
|
|
||||||
let texture = self.user_textures[id].texture.as_ref();
|
|
||||||
texture.expect("Should have been uploaded")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,31 +359,38 @@ impl Painter {
|
||||||
gl.clear(Gl::COLOR_BUFFER_BIT);
|
gl.clear(Gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
for (clip_rect, triangles) in jobs {
|
for (clip_rect, triangles) in jobs {
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(self.get_texture(triangles.texture_id)));
|
if let Some(gl_texture) = self.get_texture(triangles.texture_id) {
|
||||||
|
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));
|
||||||
|
|
||||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||||
let clip_min_x = clamp(clip_min_x, 0.0..=screen_size_pixels.x);
|
let clip_min_x = clamp(clip_min_x, 0.0..=screen_size_pixels.x);
|
||||||
let clip_min_y = clamp(clip_min_y, 0.0..=screen_size_pixels.y);
|
let clip_min_y = clamp(clip_min_y, 0.0..=screen_size_pixels.y);
|
||||||
let clip_max_x = clamp(clip_max_x, clip_min_x..=screen_size_pixels.x);
|
let clip_max_x = clamp(clip_max_x, clip_min_x..=screen_size_pixels.x);
|
||||||
let clip_max_y = clamp(clip_max_y, clip_min_y..=screen_size_pixels.y);
|
let clip_max_y = clamp(clip_max_y, clip_min_y..=screen_size_pixels.y);
|
||||||
let clip_min_x = clip_min_x.round() as i32;
|
let clip_min_x = clip_min_x.round() as i32;
|
||||||
let clip_min_y = clip_min_y.round() as i32;
|
let clip_min_y = clip_min_y.round() as i32;
|
||||||
let clip_max_x = clip_max_x.round() as i32;
|
let clip_max_x = clip_max_x.round() as i32;
|
||||||
let clip_max_y = clip_max_y.round() as i32;
|
let clip_max_y = clip_max_y.round() as i32;
|
||||||
|
|
||||||
// scissor Y coordinate is from the bottom
|
// scissor Y coordinate is from the bottom
|
||||||
gl.scissor(
|
gl.scissor(
|
||||||
clip_min_x,
|
clip_min_x,
|
||||||
self.canvas.height() as i32 - clip_max_y,
|
self.canvas.height() as i32 - clip_max_y,
|
||||||
clip_max_x - clip_min_x,
|
clip_max_x - clip_min_x,
|
||||||
clip_max_y - clip_min_y,
|
clip_max_y - clip_min_y,
|
||||||
);
|
);
|
||||||
|
|
||||||
for triangles in triangles.split_to_u16() {
|
for triangles in triangles.split_to_u16() {
|
||||||
self.paint_triangles(&triangles)?;
|
self.paint_triangles(&triangles)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crate::console_warn(format!(
|
||||||
|
"WebGL: Failed to find texture {:?}",
|
||||||
|
triangles.texture_id
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { path = "../egui", features = ["serde"] }
|
egui = { path = "../egui", features = ["serde"] }
|
||||||
egui_web = { path = "../egui_web" }
|
egui_web = { path = "../egui_web" }
|
||||||
|
image = { version = "0.23", default_features=false, features=["jpeg", "png"] }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
@ -1,18 +1,50 @@
|
||||||
use egui_web::fetch::Response;
|
use egui_web::fetch::Response;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
|
struct Image {
|
||||||
|
size: (usize, usize),
|
||||||
|
pixels: Vec<egui::Srgba>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
fn decode(bytes: &[u8]) -> Option<Image> {
|
||||||
|
use image::GenericImageView;
|
||||||
|
let image = image::load_from_memory(&bytes).ok()?;
|
||||||
|
let image_buffer = image.to_rgba();
|
||||||
|
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)
|
||||||
|
.map(|p| egui::Srgba::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(Image { size, pixels })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Resource {
|
||||||
|
/// HTTP response
|
||||||
|
response: Response,
|
||||||
|
|
||||||
|
/// If set, the response was an image.
|
||||||
|
image: Option<Image>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ExampleApp {
|
pub struct ExampleApp {
|
||||||
url: String,
|
url: String,
|
||||||
receivers: Vec<Receiver<Result<Response, String>>>,
|
in_progress: Option<Receiver<Result<Response, String>>>,
|
||||||
fetch_result: Option<Result<Response, String>>,
|
result: Option<Result<Resource, String>>,
|
||||||
|
texture_id: Option<egui::TextureId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExampleApp {
|
impl Default for ExampleApp {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
||||||
receivers: Default::default(),
|
in_progress: Default::default(),
|
||||||
fetch_result: Default::default(),
|
result: Default::default(),
|
||||||
|
texture_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +55,7 @@ impl egui::app::App for ExampleApp {
|
||||||
fn ui(
|
fn ui(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &std::sync::Arc<egui::Context>,
|
ctx: &std::sync::Arc<egui::Context>,
|
||||||
_integration_context: &mut egui::app::IntegrationContext,
|
integration_context: &mut egui::app::IntegrationContext,
|
||||||
) {
|
) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("HTTP Get inside of Egui");
|
ui.heading("HTTP Get inside of Egui");
|
||||||
|
@ -32,49 +64,24 @@ impl egui::app::App for ExampleApp {
|
||||||
"(source code)"
|
"(source code)"
|
||||||
));
|
));
|
||||||
|
|
||||||
{
|
if ui_url(ui, &mut self.url) {
|
||||||
let mut trigger_fetch = false;
|
let (sender, receiver) = std::sync::mpsc::channel();
|
||||||
|
self.in_progress = Some(receiver);
|
||||||
ui.horizontal(|ui| {
|
let url = self.url.clone();
|
||||||
ui.label("URL:");
|
egui_web::spawn_future(async move {
|
||||||
trigger_fetch |= ui.text_edit_singleline(&mut self.url).lost_kb_focus;
|
sender.send(egui_web::fetch::get(&url).await).ok();
|
||||||
|
// TODO: trigger egui repaint somehow
|
||||||
if ui.button("Egui README.md").clicked {
|
|
||||||
self.url = "https://raw.githubusercontent.com/emilk/egui/master/README.md"
|
|
||||||
.to_owned();
|
|
||||||
trigger_fetch = true;
|
|
||||||
}
|
|
||||||
if ui.button("Source code for this file").clicked {
|
|
||||||
self.url = format!(
|
|
||||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
|
||||||
file!()
|
|
||||||
);
|
|
||||||
trigger_fetch = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
trigger_fetch |= ui.button("GET").clicked;
|
|
||||||
|
|
||||||
if trigger_fetch {
|
|
||||||
let (sender, receiver) = std::sync::mpsc::channel();
|
|
||||||
self.receivers.push(receiver);
|
|
||||||
let url = self.url.clone();
|
|
||||||
|
|
||||||
let future = async move {
|
|
||||||
let result = egui_web::fetch::get_text(&url).await;
|
|
||||||
sender.send(result).ok();
|
|
||||||
// TODO: trigger egui repaint somehow
|
|
||||||
};
|
|
||||||
|
|
||||||
egui_web::spawn_future(future);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show finished download (if any):
|
ui.separator();
|
||||||
if let Some(result) = &self.fetch_result {
|
|
||||||
ui.separator();
|
if self.in_progress.is_some() {
|
||||||
|
ui.label("Please wait...");
|
||||||
|
} else if let Some(result) = &self.result {
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => {
|
Ok(resource) => {
|
||||||
ui_response(ui, response);
|
ui_resouce(ui, self.texture_id, resource);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
// This should only happen if the fetch API isn't available or something similar.
|
// This should only happen if the fetch API isn't available or something similar.
|
||||||
|
@ -84,25 +91,100 @@ impl egui::app::App for ExampleApp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for i in (0..self.receivers.len()).rev() {
|
self.poll_receiver(integration_context);
|
||||||
if let Ok(result) = self.receivers[i].try_recv() {
|
}
|
||||||
self.fetch_result = Some(result);
|
}
|
||||||
let _ = self.receivers.swap_remove(i);
|
|
||||||
|
impl ExampleApp {
|
||||||
|
fn load_image(
|
||||||
|
&mut self,
|
||||||
|
integration_context: &mut egui::app::IntegrationContext,
|
||||||
|
response: &Response,
|
||||||
|
) -> Option<Image> {
|
||||||
|
let tex_allocator = integration_context.tex_allocator.as_mut()?;
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
response.header_content_type.as_str(),
|
||||||
|
"image/jpeg" | "image/png"
|
||||||
|
) {
|
||||||
|
let image = Image::decode(&response.bytes)?;
|
||||||
|
let texture_id = self.texture_id.unwrap_or_else(|| tex_allocator.alloc());
|
||||||
|
self.texture_id = Some(texture_id);
|
||||||
|
tex_allocator.set_srgba_premultiplied(texture_id, image.size, &image.pixels);
|
||||||
|
return Some(image);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_receiver(&mut self, integration_context: &mut egui::app::IntegrationContext) {
|
||||||
|
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(|response| Resource {
|
||||||
|
image: self.load_image(integration_context, &response),
|
||||||
|
response,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_response(ui: &mut egui::Ui, response: &Response) {
|
fn ui_url(ui: &mut egui::Ui, url: &mut String) -> bool {
|
||||||
ui.monospace(format!("url: {}", response.url));
|
let mut trigger_fetch = false;
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("URL:");
|
||||||
|
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus;
|
||||||
|
|
||||||
|
if ui.button("Source code for this example").clicked {
|
||||||
|
*url = format!(
|
||||||
|
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||||
|
file!()
|
||||||
|
);
|
||||||
|
trigger_fetch = true;
|
||||||
|
}
|
||||||
|
if ui.button("Random image").clicked {
|
||||||
|
let seed = ui.input().time;
|
||||||
|
let width = 640;
|
||||||
|
let height = 480;
|
||||||
|
*url = format!("https://picsum.photos/seed/{}/{}/{}", seed, width, height);
|
||||||
|
trigger_fetch = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
trigger_fetch |= ui.button("GET").clicked;
|
||||||
|
|
||||||
|
trigger_fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_resouce(ui: &mut egui::Ui, texture_id: Option<egui::TextureId>, resource: &Resource) {
|
||||||
|
let Resource { response, image } = resource;
|
||||||
|
|
||||||
|
ui.monospace(format!("url: {}", response.url));
|
||||||
ui.monospace(format!(
|
ui.monospace(format!(
|
||||||
"status: {} ({})",
|
"status: {} ({})",
|
||||||
response.status, response.status_text
|
response.status, response.status_text
|
||||||
));
|
));
|
||||||
|
ui.monospace(format!("Content-Type: {}", response.header_content_type));
|
||||||
|
ui.monospace(format!(
|
||||||
|
"Size: {:.1} kB",
|
||||||
|
response.bytes.len() as f32 / 1000.0
|
||||||
|
));
|
||||||
|
|
||||||
ui.monospace("Body:");
|
if let Some(image) = image {
|
||||||
ui.separator();
|
if let Some(texture_id) = texture_id {
|
||||||
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
ui.image(
|
||||||
ui.monospace(&response.body);
|
texture_id,
|
||||||
});
|
egui::Vec2::new(image.size.0 as f32, image.size.1 as f32),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some(text) = &response.text {
|
||||||
|
ui.monospace("Body:");
|
||||||
|
ui.separator();
|
||||||
|
egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||||
|
ui.monospace(text);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.monospace("[binary]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue