Merge branch 'imbris/icing' into 'master'

Iced

Closes #831, #518, #529, #644, #709, #627, #739, and #734

See merge request veloren/veloren!1467
This commit is contained in:
Imbris 2020-11-11 09:28:15 +00:00
commit d8ce666a9e
128 changed files with 8455 additions and 2973 deletions

View File

@ -67,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Switched to procedural snow cover on trees
- Significantly improved terrain generation performance
- Significantly stabilized the game clock, to produce more "constant" TPS
- Transitioned main menu and character selection screen to a using iced for the ui (fixes paste keybinding on macos, removes password field limits, adds tabbing between input fields in the main menu, adds language selection in the main menu)
### Removed

395
Cargo.lock generated
View File

@ -1,5 +1,15 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ab_glyph"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a685fe66654266f321a8b572660953f4df36a2135706503a4c89981d76e1a2"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser 0.8.0",
]
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.3"
@ -117,6 +127,15 @@ dependencies = [
"num-traits 0.2.12",
]
[[package]]
name = "approx"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [
"num-traits 0.2.12",
]
[[package]]
name = "arc-swap"
version = "0.4.7"
@ -192,7 +211,7 @@ dependencies = [
"async-task",
"broadcaster",
"crossbeam-channel 0.4.4",
"crossbeam-deque",
"crossbeam-deque 0.7.3",
"crossbeam-utils 0.7.2",
"futures-core",
"futures-io",
@ -535,6 +554,46 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "clipboard-win"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5123c6b97286809fea9e38d2c9bf530edbcb9fc0d8f8272c28b0c95f067fa92d"
dependencies = [
"error-code",
"str-buf",
"winapi 0.3.9",
]
[[package]]
name = "clipboard_macos"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145a7f9e9b89453bc0a5e32d166456405d389cea5b578f57f1274b1397588a95"
dependencies = [
"objc",
"objc-foundation",
"objc_id",
]
[[package]]
name = "clipboard_wayland"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d872adca0fc88173f8b7532c651e29ce67dc97323f4546c1c8af6610937fb"
dependencies = [
"smithay-clipboard 0.5.2",
]
[[package]]
name = "clipboard_x11"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "137cbd60c42327a8d63e710cee5a4d6a1ac41cdc90449ea2c2c63bd5e186290a"
dependencies = [
"xcb",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
@ -553,21 +612,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8"
dependencies = [
"bitflags",
"block",
"core-foundation 0.7.0",
"core-graphics 0.19.2",
"foreign-types",
"libc",
"objc",
]
[[package]]
name = "cocoa"
version = "0.23.0"
@ -677,6 +721,12 @@ dependencies = [
"syn 1.0.42",
]
[[package]]
name = "const_fn"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@ -708,11 +758,11 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b"
dependencies = [
"clipboard-win",
"clipboard-win 3.1.1",
"objc",
"objc-foundation",
"objc_id",
"smithay-clipboard",
"smithay-clipboard 0.6.1",
"x11-clipboard",
]
@ -903,7 +953,7 @@ checksum = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c"
dependencies = [
"cfg-if 0.1.10",
"crossbeam-channel 0.3.9",
"crossbeam-deque",
"crossbeam-deque 0.7.3",
"crossbeam-epoch 0.7.2",
"crossbeam-queue 0.1.2",
"crossbeam-utils 0.6.6",
@ -928,6 +978,16 @@ dependencies = [
"maybe-uninit",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.0",
]
[[package]]
name = "crossbeam-deque"
version = "0.7.3"
@ -939,6 +999,17 @@ dependencies = [
"maybe-uninit",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch 0.9.0",
"crossbeam-utils 0.8.0",
]
[[package]]
name = "crossbeam-epoch"
version = "0.7.2"
@ -968,6 +1039,20 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f"
dependencies = [
"cfg-if 1.0.0",
"const_fn",
"crossbeam-utils 0.8.0",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.1.2"
@ -1009,6 +1094,18 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5"
dependencies = [
"autocfg 1.0.1",
"cfg-if 1.0.0",
"const_fn",
"lazy_static",
]
[[package]]
name = "crossterm"
version = "0.17.7"
@ -1324,6 +1421,16 @@ dependencies = [
"version_check 0.9.2",
]
[[package]]
name = "error-code"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49c94f66f2d2c5ee8685039e458b4e6c9f13af7c28736baf10ce42966a5ab52"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "euc"
version = "0.5.1"
@ -1723,6 +1830,12 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "glam"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8637c7ec4fd0776c51eeab3e0d5d1aa7e440ece3fc2ee7d674e13c957287bfc1"
[[package]]
name = "glob"
version = "0.3.0"
@ -1741,14 +1854,14 @@ dependencies = [
[[package]]
name = "glutin"
version = "0.24.1"
source = "git+https://github.com/rust-windowing/glutin.git?rev=63a1ea7d6e64c5112418cab9f21cd409f0afd7c2#63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8bae26a39a728b003e9fad473ea89527de0de050143b4df866f18bb154bc86e"
dependencies = [
"android_glue",
"cgl",
"cocoa 0.20.2",
"core-foundation 0.7.0",
"core-graphics 0.19.2",
"cocoa",
"core-foundation 0.9.1",
"glutin_egl_sys",
"glutin_emscripten_sys",
"glutin_gles2_sys",
@ -1759,8 +1872,8 @@ dependencies = [
"log",
"objc",
"osmesa-sys",
"parking_lot 0.10.2",
"wayland-client 0.27.0",
"parking_lot 0.11.0",
"wayland-client 0.28.1",
"wayland-egl",
"winapi 0.3.9",
"winit",
@ -1769,7 +1882,8 @@ dependencies = [
[[package]]
name = "glutin_egl_sys"
version = "0.1.5"
source = "git+https://github.com/rust-windowing/glutin.git?rev=63a1ea7d6e64c5112418cab9f21cd409f0afd7c2#63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211"
dependencies = [
"gl_generator",
"winapi 0.3.9",
@ -1778,12 +1892,14 @@ dependencies = [
[[package]]
name = "glutin_emscripten_sys"
version = "0.1.1"
source = "git+https://github.com/rust-windowing/glutin.git?rev=63a1ea7d6e64c5112418cab9f21cd409f0afd7c2#63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1"
[[package]]
name = "glutin_gles2_sys"
version = "0.1.5"
source = "git+https://github.com/rust-windowing/glutin.git?rev=63a1ea7d6e64c5112418cab9f21cd409f0afd7c2#63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103"
dependencies = [
"gl_generator",
"objc",
@ -1792,7 +1908,8 @@ dependencies = [
[[package]]
name = "glutin_glx_sys"
version = "0.1.7"
source = "git+https://github.com/rust-windowing/glutin.git?rev=63a1ea7d6e64c5112418cab9f21cd409f0afd7c2#63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351"
dependencies = [
"gl_generator",
"x11-dl",
@ -1801,11 +1918,51 @@ dependencies = [
[[package]]
name = "glutin_wgl_sys"
version = "0.1.5"
source = "git+https://github.com/rust-windowing/glutin.git?rev=63a1ea7d6e64c5112418cab9f21cd409f0afd7c2#63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696"
dependencies = [
"gl_generator",
]
[[package]]
name = "glyph_brush"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afd3e2cfd503a5218dd56172a8bf7c8655a4a7cf745737c606a6edfeea1b343f"
dependencies = [
"glyph_brush_draw_cache",
"glyph_brush_layout",
"log",
"ordered-float 1.1.0",
"rustc-hash",
"twox-hash",
]
[[package]]
name = "glyph_brush_draw_cache"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cef969a091be5565c2c10b31fd2f115cbeed9f783a27c96ae240ff8ceee067c"
dependencies = [
"ab_glyph",
"crossbeam-channel 0.5.0",
"crossbeam-deque 0.8.0",
"linked-hash-map",
"rayon",
"rustc-hash",
]
[[package]]
name = "glyph_brush_layout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10bc06d530bf20c1902f1b02799ab7372ff43f6119770c49b0bc3f21bd148820"
dependencies = [
"ab_glyph",
"approx 0.4.0",
"xi-unicode",
]
[[package]]
name = "guillotiere"
version = "0.5.2"
@ -1988,6 +2145,69 @@ dependencies = [
"want",
]
[[package]]
name = "iced_core"
version = "0.2.1"
source = "git+https://github.com/hecrj/iced?rev=f464316#f46431600cb61d4e83e0ded1ca79525478436be3"
[[package]]
name = "iced_futures"
version = "0.1.2"
source = "git+https://github.com/hecrj/iced?rev=f464316#f46431600cb61d4e83e0ded1ca79525478436be3"
dependencies = [
"futures 0.3.5",
"log",
"wasm-bindgen-futures",
]
[[package]]
name = "iced_graphics"
version = "0.1.0"
source = "git+https://github.com/hecrj/iced?rev=f464316#f46431600cb61d4e83e0ded1ca79525478436be3"
dependencies = [
"bytemuck",
"glam",
"iced_native",
"iced_style",
"raw-window-handle",
"thiserror",
]
[[package]]
name = "iced_native"
version = "0.2.2"
source = "git+https://github.com/hecrj/iced?rev=f464316#f46431600cb61d4e83e0ded1ca79525478436be3"
dependencies = [
"iced_core",
"iced_futures",
"num-traits 0.2.12",
"twox-hash",
"unicode-segmentation",
]
[[package]]
name = "iced_style"
version = "0.1.0"
source = "git+https://github.com/hecrj/iced?rev=f464316#f46431600cb61d4e83e0ded1ca79525478436be3"
dependencies = [
"iced_core",
]
[[package]]
name = "iced_winit"
version = "0.1.1"
source = "git+https://github.com/hecrj/iced?rev=f464316#f46431600cb61d4e83e0ded1ca79525478436be3"
dependencies = [
"iced_futures",
"iced_graphics",
"iced_native",
"log",
"thiserror",
"winapi 0.3.9",
"window_clipboard",
"winit",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@ -2916,9 +3136,9 @@ dependencies = [
[[package]]
name = "old_school_gfx_glutin_ext"
version = "0.24.0"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0557cea37cc48d238c938ded2873a6cc772704ee1eb01e832b43c2dd99624bc"
checksum = "97d3bf7a77b32b947b6eaa3bc3671d50a74cd9aafdbbd4f9a4feb03ed3a0ee94"
dependencies = [
"gfx_core",
"gfx_device_gl",
@ -3010,7 +3230,16 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3"
dependencies = [
"ttf-parser",
"ttf-parser 0.6.2",
]
[[package]]
name = "owned_ttf_parser"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb477c7fd2a3a6e04e1dc6ca2e4e9b04f2df702021dc5a5d1cf078c587dc59f7"
dependencies = [
"ttf-parser 0.8.2",
]
[[package]]
@ -3525,7 +3754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270"
dependencies = [
"autocfg 1.0.1",
"crossbeam-deque",
"crossbeam-deque 0.7.3",
"either",
"rayon-core",
]
@ -3537,7 +3766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf"
dependencies = [
"crossbeam-channel 0.4.4",
"crossbeam-deque",
"crossbeam-deque 0.7.3",
"crossbeam-utils 0.7.2",
"lazy_static",
"num_cpus",
@ -3708,8 +3937,8 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0"
dependencies = [
"approx",
"crossbeam-deque",
"approx 0.3.2",
"crossbeam-deque 0.7.3",
"crossbeam-utils 0.7.2",
"linked-hash-map",
"num_cpus",
@ -3725,7 +3954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
"owned_ttf_parser 0.6.0",
]
[[package]]
@ -4007,10 +4236,8 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562da6f2f0836e144f2e92118b35add58368280556af94f399666ebfd7d1e731"
dependencies = [
"andrew",
"bitflags",
"byteorder",
"calloop",
"dlib",
"lazy_static",
"log",
@ -4027,8 +4254,10 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ec5c077def8af49f9b5aeeb5fcf8079c638c6615c3a8f9305e2dea601de57f7"
dependencies = [
"andrew",
"bitflags",
"byteorder",
"calloop",
"dlib",
"lazy_static",
"log",
@ -4039,6 +4268,16 @@ dependencies = [
"wayland-protocols 0.28.1",
]
[[package]]
name = "smithay-clipboard"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e9db50a9b272938b767b731a1291f22f407315def4049db93871e8828034d5"
dependencies = [
"smithay-client-toolkit 0.11.0",
"wayland-client 0.27.0",
]
[[package]]
name = "smithay-clipboard"
version = "0.6.1"
@ -4173,6 +4412,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "string"
version = "0.2.1"
@ -4472,7 +4717,7 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89"
dependencies = [
"crossbeam-deque",
"crossbeam-deque 0.7.3",
"crossbeam-queue 0.2.3",
"crossbeam-utils 0.7.2",
"futures 0.1.29",
@ -4646,6 +4891,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
[[package]]
name = "ttf-parser"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d973cfa0e6124166b50a1105a67c85de40bbc625082f35c0f56f84cb1fb0a827"
[[package]]
name = "tui"
version = "0.10.0"
@ -4669,6 +4920,9 @@ name = "twox-hash"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56"
dependencies = [
"rand 0.7.3",
]
[[package]]
name = "tynm"
@ -4815,7 +5069,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2657d8704e5e0be82b60157c8dbc71a269273ad766984508fdc54030a0690c4d"
dependencies = [
"approx",
"approx 0.3.2",
"num-integer",
"num-traits 0.2.12",
"rustc_version",
@ -4828,7 +5082,7 @@ name = "vek"
version = "0.12.0"
source = "git+https://gitlab.com/veloren/vek.git?branch=fix_intrinsics#237a78528b505f34f6dde5dc77db3b642388fe4a"
dependencies = [
"approx",
"approx 0.3.2",
"num-integer",
"num-traits 0.2.12",
"rustc_version",
@ -4987,8 +5241,11 @@ dependencies = [
"git2",
"glsl-include",
"glutin",
"glyph_brush",
"guillotiere",
"hashbrown 0.7.2",
"iced_native",
"iced_winit",
"image",
"inline_tweak",
"itertools",
@ -5014,6 +5271,7 @@ dependencies = [
"veloren-server",
"veloren-voxygen-anim",
"veloren-world",
"window_clipboard",
"winit",
"winres",
]
@ -5171,6 +5429,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
dependencies = [
"cfg-if 0.1.10",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.68"
@ -5280,12 +5550,12 @@ dependencies = [
[[package]]
name = "wayland-egl"
version = "0.27.0"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123b47be6f258fffd854f016e8e7397adb8c04d984fcf308dce13714ae2231ae"
checksum = "e7ca6190c84bcdc58beccc619bf4866709db32d653255e89da38867f97f90d61"
dependencies = [
"wayland-client 0.27.0",
"wayland-sys 0.27.0",
"wayland-client 0.28.1",
"wayland-sys 0.28.1",
]
[[package]]
@ -5448,13 +5718,26 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "window_clipboard"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b849e24b344ea3535bcda7320b8b7f3560bd2c3692de73153d3c64acc84203e5"
dependencies = [
"clipboard-win 4.0.3",
"clipboard_macos",
"clipboard_wayland",
"clipboard_x11",
"raw-window-handle",
]
[[package]]
name = "winit"
version = "0.22.2"
source = "git+https://gitlab.com/veloren/winit.git?branch=macos-test-rebased#5efbaa7e4644c627201a9c4d24217f448795ce0f"
version = "0.23.0"
source = "git+https://gitlab.com/veloren/winit.git?branch=macos-test-spiffed#7c8c5f21384c898f50d37298d229093549b08803"
dependencies = [
"bitflags",
"cocoa 0.23.0",
"cocoa",
"core-foundation 0.9.1",
"core-graphics 0.22.1",
"core-video-sys",
@ -5473,8 +5756,8 @@ dependencies = [
"percent-encoding 2.1.0",
"raw-window-handle",
"serde",
"smithay-client-toolkit 0.11.0",
"wayland-client 0.27.0",
"smithay-client-toolkit 0.12.0",
"wayland-client 0.28.1",
"winapi 0.3.9",
"x11-dl",
]
@ -5544,6 +5827,12 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
[[package]]
name = "xi-unicode"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
[[package]]
name = "xml-rs"
version = "0.8.3"

View File

@ -79,6 +79,5 @@ debug = 1
[patch.crates-io]
# cpal conflict fix isn't released yet
winit = { git = "https://gitlab.com/veloren/winit.git", branch = "macos-test-rebased" }
glutin = {git = "https://github.com/rust-windowing/glutin.git", rev="63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"}
winit = { git = "https://gitlab.com/veloren/winit.git", branch = "macos-test-spiffed" }
vek = { git = "https://gitlab.com/veloren/vek.git", branch = "fix_intrinsics" }

BIN
assets/voxygen/element/buttons/button.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/buttons/x_red.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/x_red.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/buttons/x_red_hover.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/buttons/x_red_press.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/banner.png (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/banner_gradient_bottom.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/tooltip/corner.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/tooltip/edge.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/window_4.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/misc_bg/textbox.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
/// Localization for Polish / Tłumaczenia dla języka polskiego
VoxygenLocalization(
(
metadata: (
language_name: "Polish",
language_identifier: "PL",

View File

@ -11,7 +11,7 @@
/// `assets/voxygen/i18n` and that's it!
/// Lokalisation für Deutsch/Deutschland
VoxygenLocalization(
(
metadata: (
language_name: "Deutsch",
language_identifier: "de_DE",
@ -470,10 +470,10 @@ magischen Gegenstände ergattern?"#,
"char_selection.change_server": "Server wechseln.",
"char_selection.enter_world": "Betreten",
"char_selection.logout": "Ausloggen",
"char_selection.create_charater": "Charakter erstellen",
"char_selection.create_character": "Charakter erstellen",
"char_selection.create_new_character": "Neuen Charakter erstellen",
"char_selection.creating_character": "Erstelle Charakter...",
"char_selection.character_creation": "Charaktererstellung",
"char_selection.create_new_charater": "Neuen Charakter erstellen",
"char_selection.human_default": "Human Default",
"char_selection.level_fmt": "Level {level_nb}",

View File

@ -13,7 +13,7 @@
/// WARNING: Localization files shall be saved in UTF-8 format without BOM
/// Localization for "global" English
VoxygenLocalization(
(
metadata: (
language_name: "English",
language_identifier: "en",
@ -65,6 +65,7 @@ VoxygenLocalization(
"common.back": "Back",
"common.create": "Create",
"common.okay": "Okay",
"common.add": "Add",
"common.accept": "Accept",
"common.decline": "Decline",
"common.disclaimer": "Disclaimer",
@ -107,6 +108,9 @@ Is the client up to date?"#,
/// Start Main screen section
"main.username": "Username",
"main.server": "Server",
"main.password": "Password",
"main.connecting": "Connecting",
"main.creating_world": "Creating world",
"main.tip": "Tip:",
@ -154,6 +158,9 @@ https://veloren.net/account/."#,
"main.login.not_on_whitelist": "You need a Whitelist entry by an Admin to join",
"main.login.banned": "You have been banned with the following reason",
"main.login.kicked": "You have been kicked with the following reason",
"main.login.select_language": "Select a language",
"main.servers.select_server": "Select a server",
/// End Main screen section
@ -476,7 +483,7 @@ magically infused items?"#,
"char_selection.change_server": "Change Server",
"char_selection.enter_world": "Enter World",
"char_selection.logout": "Logout",
"char_selection.create_new_charater": "Create New Character",
"char_selection.create_new_character": "Create New Character",
"char_selection.creating_character": "Creating Character...",
"char_selection.character_creation": "Character Creation",
@ -493,7 +500,7 @@ magically infused items?"#,
"char_selection.accessories": "Accessories",
"char_selection.create_info_name": "Your Character needs a name!",
/// End chracter selection section
/// End character selection section
/// Start character window section

View File

@ -12,7 +12,7 @@
///
/// Localization for Spanish (Spain)
VoxygenLocalization(
(
metadata: (
language_name: "Español de España",
language_identifier: "es_ES",
@ -358,7 +358,7 @@ objetos imbuidos de magia?"#,
"char_selection.change_server": "Cambiar de servidor",
"char_selection.enter_world": "Entrar al mundo",
"char_selection.logout": "Salir",
"char_selection.create_new_charater": "Crear nuevo personaje",
"char_selection.create_new_character": "Crear nuevo personaje",
"char_selection.creating_character": "Creando personaje...",
"char_selection.character_creation": "Creación de personaje",

View File

@ -13,7 +13,7 @@
/// WARNING: Localization files shall be saved in UTF-8 format without BOM
/// Localization for "latinoamericano" Latin-American
VoxygenLocalization(
(
metadata: (
language_name: "Español Latino",
language_identifier: "es_la",

View File

@ -1,5 +1,5 @@
/// Localization for French (France locale)
VoxygenLocalization(
(
metadata: (
language_name: "Français",
language_identifier: "fr_FR",
@ -372,7 +372,7 @@ objets magiques ?"#,
"char_selection.change_server": "Changer de serveur",
"char_selection.enter_world": "Entrer dans le monde",
"char_selection.logout": "Se déconnecter",
"char_selection.create_new_charater": "Créer un nouveau personnage",
"char_selection.create_new_character": "Créer un nouveau personnage",
"char_selection.creating_character": "Création du personnage...",
"char_selection.character_creation": "Création de personnage",

View File

@ -14,7 +14,7 @@
/// Localization for "global" Italian
VoxygenLocalization(
(
metadata: (
language_name: "Italiano",
language_identifier: "it_IT",
@ -462,7 +462,7 @@ oggetti infusi di magia?"#,
"char_selection.change_server": "Cambia Server",
"char_selection.enter_world": "Unisciti al Mondo",
"char_selection.logout": "Disconnettiti",
"char_selection.create_new_charater": "Crea un nuovo Personaggio",
"char_selection.create_new_character": "Crea un nuovo Personaggio",
"char_selection.creating_character": "Creazione Personaggio...",
"char_selection.character_creation": "Creazione Personaggio",

View File

@ -13,7 +13,7 @@
/// WARNING: Localization files shall be saved in UTF-8 format without BOM
/// Localization for "global" English
VoxygenLocalization(
(
metadata: (
language_name: "Nederlands",
language_identifier: "nl",

View File

@ -1,5 +1,5 @@
/// Localization for Portuguese (Brazil)
VoxygenLocalization(
(
metadata: (
language_name: "Português Brasileiro",
language_identifier: "pt_BR",

View File

@ -1,5 +1,5 @@
/// Localization for portuguese (Portugal)
VoxygenLocalization(
(
metadata: (
language_name: "Português",
language_identifier: "pt_PT",
@ -343,7 +343,7 @@ Comandos de chat:
"char_selection.change_server": "Mudar de servidor",
"char_selection.enter_world": "Entrar no mundo",
"char_selection.logout": "Desconectar",
"char_selection.create_new_charater": "Criar nova personagem",
"char_selection.create_new_character": "Criar nova personagem",
"char_selection.character_creation": "Criação de personagem",
"char_selection.human_default": "Humano padrão",

View File

@ -1,5 +1,5 @@
/// Localization for "global" Russian
VoxygenLocalization(
(
metadata: (
language_name: "Русский",
language_identifier: "ru_RU",
@ -400,7 +400,7 @@ https://veloren.net/account/."#,
"char_selection.change_server": "Сменить сервер",
"char_selection.enter_world": "Войти в мир",
"char_selection.logout": "Выйти в меню",
"char_selection.create_new_charater": "Создать нового персонажа",
"char_selection.create_new_character": "Создать нового персонажа",
"char_selection.creating_character": "Создание персонажа...",
"char_selection.character_creation": "Создание персонажа",

View File

@ -11,7 +11,7 @@
/// `assets/voxygen/i18n` and that's it!
/// Localization for Swedish
VoxygenLocalization(
(
metadata: (
language_name: "Svenska",
language_identifier: "sv",

View File

@ -13,7 +13,7 @@
/// WARNING: Localization files shall be saved in UTF-8 format without BOM
/// Localization for Turkish (Turkey)
VoxygenLocalization(
(
metadata: (
language_name: "Türkçe (Türkiye)",
language_identifier: "tr_TR",

View File

@ -13,7 +13,7 @@
/// 注意: 本地化文件应以 UTF-8无BOM 格式保存
/// "全局"本地化 Simplified Chinese-简体中文
VoxygenLocalization(
(
metadata: (
language_name: "Simplified Chinese",
language_identifier: "zh_CN",

View File

@ -1,5 +1,5 @@
/// Localization for Traditional Chinese
VoxygenLocalization(
(
metadata: (
language_name: "繁體中文",
language_identifier: "zh_TW",

View File

@ -58,7 +58,7 @@ impl Body {
self.hair_color = self.hair_color.min(self.species.num_hair_colors() - 1);
self.skin = self.skin.min(self.species.num_skin_colors() - 1);
self.eyes = self.eyes.min(self.species.num_eyes(self.body_type) - 1);
self.eye_color = self.hair_style.min(self.species.num_eye_colors() - 1);
self.eye_color = self.eye_color.min(self.species.num_eye_colors() - 1);
self.accessory = self
.accessory
.min(self.species.num_accessories(self.body_type) - 1);

View File

@ -24,15 +24,21 @@ common = {package = "veloren-common", path = "../common"}
anim = {package = "veloren-voxygen-anim", path = "src/anim", default-features = false}
# Graphics
conrod_core = {git = "https://gitlab.com/veloren/conrod.git", branch="copypasta_0.7"}
conrod_winit = {git = "https://gitlab.com/veloren/conrod.git", branch="copypasta_0.7"}
euc = {git = "https://github.com/zesterer/euc.git"}
gfx = "0.18.2"
gfx_device_gl = {version = "0.16.2", optional = true}
gfx_gl = {version = "0.6.1", optional = true}
glutin = {git = "https://github.com/rust-windowing/glutin.git", rev="63a1ea7d6e64c5112418cab9f21cd409f0afd7c2"}
old_school_gfx_glutin_ext = "0.24"
winit = {version = "0.22.2", features = ["serde"]}
glutin = "0.25.1"
old_school_gfx_glutin_ext = "0.25"
winit = {version = "0.23.0", features = ["serde"]}
# Ui
conrod_core = {git = "https://gitlab.com/veloren/conrod.git", branch="copypasta_0.7"}
conrod_winit = {git = "https://gitlab.com/veloren/conrod.git", branch="copypasta_0.7"}
euc = {git = "https://github.com/zesterer/euc.git"}
iced = {package = "iced_native", git = "https://github.com/hecrj/iced", rev = "f464316"}
iced_winit = {git = "https://github.com/hecrj/iced", rev = "f464316"}
window_clipboard = "0.1.1"
glyph_brush = "0.7.0"
# ECS
specs = {git = "https://github.com/amethyst/specs.git", rev = "7a2e348ab2223818bad487695c66c43db88050a5"}

View File

@ -8,9 +8,9 @@ use super::{
};
use crate::{
hud::get_quality_col,
i18n::VoxygenLocalization,
i18n::Localization,
ui::{
fonts::ConrodVoxygenFonts,
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, Tooltip, TooltipManager, Tooltipable,
},
@ -90,14 +90,14 @@ pub struct Bag<'a> {
client: &'a Client,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
slot_manager: &'a mut SlotManager,
_pulse: f32,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
stats: &'a Stats,
show: &'a Show,
@ -109,12 +109,12 @@ impl<'a> Bag<'a> {
client: &'a Client,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
stats: &'a Stats,
show: &'a Show,
) -> Self {

View File

@ -4,8 +4,8 @@ use super::{
};
use crate::{
hud::{get_buff_info, BuffPosition},
i18n::VoxygenLocalization,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
i18n::Localization,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
GlobalState,
};
@ -34,12 +34,12 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct BuffsBar<'a> {
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
buffs: &'a Buffs,
pulse: f32,
global_state: &'a GlobalState,
@ -49,10 +49,10 @@ impl<'a> BuffsBar<'a> {
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn new(
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
buffs: &'a Buffs,
pulse: f32,
global_state: &'a GlobalState,

View File

@ -3,8 +3,8 @@ use super::{
BLACK, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR,
};
use crate::{
i18n::VoxygenLocalization,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
i18n::Localization,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
window::GameInput,
GlobalState,
};
@ -49,13 +49,13 @@ pub struct Buttons<'a> {
client: &'a Client,
show_bag: bool,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
global_state: &'a GlobalState,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
stats: &'a Stats,
}
@ -65,11 +65,11 @@ impl<'a> Buttons<'a> {
client: &'a Client,
show_bag: bool,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
global_state: &'a GlobalState,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
stats: &'a Stats,
) -> Self {
Self {

View File

@ -2,7 +2,7 @@ use super::{
img_ids::Imgs, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR, LOOT_COLOR,
OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, WORLD_COLOR,
};
use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts, GlobalState};
use crate::{i18n::Localization, ui::fonts::Fonts, GlobalState};
use client::{cmd, Client};
use common::{
comp::{
@ -52,7 +52,7 @@ pub struct Chat<'a> {
global_state: &'a GlobalState,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -60,7 +60,7 @@ pub struct Chat<'a> {
// TODO: add an option to adjust this
history_max: usize,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
}
impl<'a> Chat<'a> {
@ -69,8 +69,8 @@ impl<'a> Chat<'a> {
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
) -> Self {
Self {
new_messages,
@ -536,12 +536,7 @@ fn do_tab_completion(cursor: usize, input: &str, word: &str) -> (String, usize)
}
}
fn cursor_offset_to_index(
offset: usize,
text: &str,
ui: &Ui,
fonts: &ConrodVoxygenFonts,
) -> Option<Index> {
fn cursor_offset_to_index(offset: usize, text: &str, ui: &Ui, fonts: &Fonts) -> Option<Index> {
// This moves the cursor to the given offset. Conrod is a pain.
//
// Width and font must match that of the chat TextEdit

View File

@ -5,8 +5,8 @@ use super::{
};
use crate::{
hud::get_quality_col,
i18n::VoxygenLocalization,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
i18n::Localization,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
};
use client::{self, Client};
use common::comp::{
@ -55,8 +55,8 @@ pub enum Event {
pub struct Crafting<'a> {
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_imgs: &'a ItemImgs,
@ -69,8 +69,8 @@ impl<'a> Crafting<'a> {
pub fn new(
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
item_imgs: &'a ItemImgs,

View File

@ -1,5 +1,5 @@
use super::{img_ids::Imgs, settings_window::SettingsTab, TEXT_COLOR};
use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts};
use crate::{i18n::Localization, ui::fonts::Fonts};
use conrod_core::{
widget::{self, Button, Image},
widget_ids, Color, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
@ -22,19 +22,15 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct EscMenu<'a> {
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
impl<'a> EscMenu<'a> {
pub fn new(
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
) -> Self {
pub fn new(imgs: &'a Imgs, fonts: &'a Fonts, localized_strings: &'a Localization) -> Self {
Self {
imgs,
fonts,

View File

@ -6,9 +6,9 @@ use super::{
use crate::{
hud::get_buff_info,
i18n::VoxygenLocalization,
i18n::Localization,
settings::Settings,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
window::GameInput,
GlobalState,
};
@ -70,8 +70,8 @@ pub struct Group<'a> {
settings: &'a Settings,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
pulse: f32,
global_state: &'a GlobalState,
tooltip_manager: &'a mut TooltipManager,
@ -88,8 +88,8 @@ impl<'a> Group<'a> {
settings: &'a Settings,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
pulse: f32,
global_state: &'a GlobalState,
tooltip_manager: &'a mut TooltipManager,

View File

@ -3,8 +3,8 @@ use super::{
Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::{
i18n::VoxygenLocalization,
ui::{fonts::ConrodVoxygenFonts, img_ids, ImageSlider},
i18n::Localization,
ui::{fonts::Fonts, img_ids, ImageSlider},
GlobalState,
};
use client::{self, Client};
@ -41,11 +41,11 @@ pub struct Map<'a> {
world_map: &'a (img_ids::Rotations, Vec2<u32>),
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
_pulse: f32,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
global_state: &'a GlobalState,
}
impl<'a> Map<'a> {
@ -56,9 +56,9 @@ impl<'a> Map<'a> {
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
pulse: f32,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
global_state: &'a GlobalState,
) -> Self {
Self {

View File

@ -2,7 +2,7 @@ use super::{
img_ids::{Imgs, ImgsRot},
Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::ui::{fonts::ConrodVoxygenFonts, img_ids};
use crate::ui::{fonts::Fonts, img_ids};
use client::{self, Client};
use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
use conrod_core::{
@ -40,7 +40,7 @@ pub struct MiniMap<'a> {
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
ori: Vec3<f32>,
@ -53,7 +53,7 @@ impl<'a> MiniMap<'a> {
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
ori: Vec3<f32>,
) -> Self {
Self {

View File

@ -46,13 +46,13 @@ use spell::Spell;
use crate::{
ecs::{comp as vcomp, comp::HpFloaterList},
hud::img_ids::ImgsRot,
i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization},
i18n::{i18n_asset_key, LanguageMetadata, Localization},
render::{Consts, Globals, RenderMode, Renderer},
scene::{
camera::{self, Camera},
lod,
},
ui::{fonts::ConrodVoxygenFonts, img_ids::Rotations, slot, Graphic, Ingameable, ScaleMode, Ui},
ui::{fonts::Fonts, img_ids::Rotations, slot, Graphic, Ingameable, ScaleMode, Ui},
window::{Event as WinEvent, FullScreenSettings, GameInput},
GlobalState,
};
@ -598,7 +598,7 @@ pub struct Hud {
world_map: (/* Id */ Rotations, Vec2<u32>),
imgs: Imgs,
item_imgs: ItemImgs,
fonts: ConrodVoxygenFonts,
fonts: Fonts,
rot_imgs: ImgsRot,
new_messages: VecDeque<comp::ChatMsg>,
new_notifications: VecDeque<common::msg::Notification>,
@ -614,7 +614,7 @@ pub struct Hud {
tab_complete: Option<String>,
pulse: f32,
velocity: f32,
voxygen_i18n: std::sync::Arc<VoxygenLocalization>,
i18n: std::sync::Arc<Localization>,
slot_manager: slots::SlotManager,
hotbar: hotbar::State,
events: Vec<Event>,
@ -649,12 +649,11 @@ impl Hud {
// Load item images.
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
// Load language.
let voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// Load fonts.
let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui)
.expect("Impossible to load fonts!");
let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts!");
// Get the server name.
let server = &client.server_info.name;
// Get the id, unwrap is safe because this CANNOT be None at this
@ -715,7 +714,7 @@ impl Hud {
tab_complete: None,
pulse: 0.0,
velocity: 0.0,
voxygen_i18n,
i18n,
slot_manager,
hotbar: hotbar_state,
events: Vec::new(),
@ -723,10 +722,10 @@ impl Hud {
}
}
pub fn update_language(&mut self, voxygen_i18n: std::sync::Arc<VoxygenLocalization>) {
self.voxygen_i18n = voxygen_i18n;
self.fonts = ConrodVoxygenFonts::load(&self.voxygen_i18n.fonts, &mut self.ui)
.expect("Impossible to load fonts!");
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>) {
self.i18n = i18n;
self.fonts =
Fonts::load(&self.i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
}
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
@ -1256,7 +1255,7 @@ impl Hud {
in_group,
&global_state.settings.gameplay,
self.pulse,
&self.voxygen_i18n,
&self.i18n,
&self.imgs,
&self.fonts,
)
@ -1459,8 +1458,8 @@ impl Hud {
Intro::Show => {
if self.pulse > 20.0 {
self.show.want_grab = false;
let quest_headline = &self.voxygen_i18n.get("hud.temp_quest_headline");
let quest_text = &self.voxygen_i18n.get("hud.temp_quest_text");
let quest_headline = &self.i18n.get("hud.temp_quest_headline");
let quest_text = &self.i18n.get("hud.temp_quest_text");
Image::new(self.imgs.quest_bg)
.w_h(404.0, 858.0)
.middle_of(ui_widgets.window)
@ -1497,7 +1496,7 @@ impl Hud {
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.mid_bottom_with_margin_on(self.ids.q_text_bg, -120.0)
.label(&self.voxygen_i18n.get("common.accept"))
.label(&self.i18n.get("common.accept"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(self.fonts.cyri.scale(22))
.label_color(TEXT_COLOR)
@ -1675,7 +1674,7 @@ impl Hud {
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.press_key_to_toggle_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()),
)
@ -1693,7 +1692,7 @@ impl Hud {
{
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.press_key_to_toggle_debug_info_fmt")
.replace("{key}", toggle_debug_key.to_string().as_str()),
)
@ -1708,7 +1707,7 @@ impl Hud {
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.press_key_to_show_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()),
)
@ -1726,7 +1725,7 @@ impl Hud {
{
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.press_key_to_show_debug_info_fmt")
.replace("{key}", toggle_debug_key.to_string().as_str()),
)
@ -1744,7 +1743,7 @@ impl Hud {
{
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.press_key_to_toggle_lantern_fmt")
.replace("{key}", toggle_lantern_key.to_string().as_str()),
)
@ -1789,7 +1788,7 @@ impl Hud {
global_state,
&self.rot_imgs,
tooltip_manager,
&self.voxygen_i18n,
&self.i18n,
&player_stats,
)
.set(self.ids.buttons, ui_widgets)
@ -1811,7 +1810,7 @@ impl Hud {
&self.fonts,
&self.rot_imgs,
tooltip_manager,
&self.voxygen_i18n,
&self.i18n,
&player_buffs,
self.pulse,
&global_state,
@ -1831,7 +1830,7 @@ impl Hud {
&self.imgs,
&self.rot_imgs,
&self.fonts,
&self.voxygen_i18n,
&self.i18n,
self.pulse,
&global_state,
tooltip_manager,
@ -1848,7 +1847,7 @@ impl Hud {
}
// Popup (waypoint saved and similar notifications)
Popup::new(
&self.voxygen_i18n,
&self.i18n,
client,
&self.new_notifications,
&self.fonts,
@ -1884,7 +1883,7 @@ impl Hud {
tooltip_manager,
&mut self.slot_manager,
self.pulse,
&self.voxygen_i18n,
&self.i18n,
&player_stats,
&self.show,
)
@ -1951,7 +1950,7 @@ impl Hud {
&self.hotbar,
tooltip_manager,
&mut self.slot_manager,
&self.voxygen_i18n,
&self.i18n,
&self.show,
)
.set(self.ids.skillbar, ui_widgets);
@ -1965,7 +1964,7 @@ impl Hud {
client,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
&self.i18n,
&self.rot_imgs,
tooltip_manager,
&self.item_imgs,
@ -2004,7 +2003,7 @@ impl Hud {
global_state,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
&self.i18n,
)
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
.and_then(self.tab_complete.take(), |c, input| {
@ -2041,7 +2040,7 @@ impl Hud {
&self.show,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
&self.i18n,
fps as f32,
)
.set(self.ids.settings_window, ui_widgets)
@ -2194,7 +2193,7 @@ impl Hud {
client,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
&self.i18n,
info.selected_entity,
&self.rot_imgs,
tooltip_manager,
@ -2222,13 +2221,7 @@ impl Hud {
// Spellbook
if self.show.spell {
match Spell::new(
&self.show,
client,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
)
match Spell::new(&self.show, client, &self.imgs, &self.fonts, &self.i18n)
.set(self.ids.spell, ui_widgets)
{
Some(spell::Event::Close) => {
@ -2249,7 +2242,7 @@ impl Hud {
&self.world_map,
&self.fonts,
self.pulse,
&self.voxygen_i18n,
&self.i18n,
&global_state,
)
.set(self.ids.map, ui_widgets)
@ -2268,7 +2261,7 @@ impl Hud {
}
if self.show.esc_menu {
match EscMenu::new(&self.imgs, &self.fonts, &self.voxygen_i18n)
match EscMenu::new(&self.imgs, &self.fonts, &self.i18n)
.set(self.ids.esc_menu, ui_widgets)
{
Some(esc_menu::Event::OpenSettings(tab)) => {
@ -2311,7 +2304,7 @@ impl Hud {
if self.show.free_look {
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()),
)
@ -2322,7 +2315,7 @@ impl Hud {
.set(self.ids.free_look_bg, ui_widgets);
Text::new(
&self
.voxygen_i18n
.i18n
.get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()),
)
@ -2336,13 +2329,13 @@ impl Hud {
// Auto walk indicator
if self.show.auto_walk {
Text::new(&self.voxygen_i18n.get("hud.auto_walk_indicator"))
Text::new(&self.i18n.get("hud.auto_walk_indicator"))
.color(TEXT_BG)
.mid_top_with_margin_on(ui_widgets.window, 70.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.auto_walk_bg, ui_widgets);
Text::new(&self.voxygen_i18n.get("hud.auto_walk_indicator"))
Text::new(&self.i18n.get("hud.auto_walk_indicator"))
.color(KILL_COLOR)
.top_left_with_margins_on(self.ids.auto_walk_bg, -1.0, -1.0)
.font_id(self.fonts.cyri.conrod_id)

View File

@ -4,9 +4,9 @@ use super::{
};
use crate::{
hud::get_buff_info,
i18n::VoxygenLocalization,
i18n::Localization,
settings::GameplaySettings,
ui::{fonts::ConrodVoxygenFonts, Ingameable},
ui::{fonts::Fonts, Ingameable},
};
use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stats};
use conrod_core::{
@ -76,9 +76,9 @@ pub struct Overhead<'a> {
in_group: bool,
settings: &'a GameplaySettings,
pulse: f32,
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
i18n: &'a Localization,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -93,9 +93,9 @@ impl<'a> Overhead<'a> {
in_group: bool,
settings: &'a GameplaySettings,
pulse: f32,
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
i18n: &'a Localization,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
) -> Self {
Self {
info,
@ -104,7 +104,7 @@ impl<'a> Overhead<'a> {
in_group,
settings,
pulse,
voxygen_i18n,
i18n,
imgs,
fonts,
common: widget::CommonBuilder::default(),
@ -336,7 +336,7 @@ impl<'a> Widget for Overhead<'a> {
.set(state.ids.health_bar, ui);
let mut txt = format!("{}/{}", health_cur_txt, health_max_txt);
if health.is_dead {
txt = self.voxygen_i18n.get("hud.group.dead").to_string()
txt = self.i18n.get("hud.group.dead").to_string()
};
Text::new(&txt)
.mid_top_with_margin_on(state.ids.health_bar_bg, 2.0)
@ -420,8 +420,7 @@ impl<'a> Widget for Overhead<'a> {
// Speech bubble
if let Some(bubble) = self.bubble {
let dark_mode = self.settings.speech_bubble_dark_mode;
let localizer =
|s: &str, i| -> String { self.voxygen_i18n.get_variation(&s, i).to_string() };
let localizer = |s: &str, i| -> String { self.i18n.get_variation(&s, i).to_string() };
let bubble_contents: String = bubble.message(localizer);
let (text_color, shadow_color) = bubble_color(&bubble, dark_mode);
let mut text = Text::new(&bubble_contents)

View File

@ -1,6 +1,6 @@
use crate::{
settings::ControlSettings,
ui::{fonts::ConrodVoxygenFonts, Ingameable},
ui::{fonts::Fonts, Ingameable},
window::GameInput,
};
use conrod_core::{
@ -25,7 +25,7 @@ widget_ids! {
pub struct Overitem<'a> {
name: &'a str,
distance_from_player_sqr: &'a f32,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
controls: &'a ControlSettings,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -35,7 +35,7 @@ impl<'a> Overitem<'a> {
pub fn new(
name: &'a str,
distance_from_player_sqr: &'a f32,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
controls: &'a ControlSettings,
) -> Self {
Self {

View File

@ -1,5 +1,5 @@
use super::Show;
use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts};
use crate::{i18n::Localization, ui::fonts::Fonts};
use client::{self, Client};
use common::msg::Notification;
use conrod_core::{
@ -21,10 +21,10 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct Popup<'a> {
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
i18n: &'a Localization,
client: &'a Client,
new_notifications: &'a VecDeque<Notification>,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
show: &'a Show,
@ -34,14 +34,14 @@ pub struct Popup<'a> {
/// Dungeon Cleared (TODO), and Quest Completed (TODO)
impl<'a> Popup<'a> {
pub fn new(
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
i18n: &'a Localization,
client: &'a Client,
new_notifications: &'a VecDeque<Notification>,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
show: &'a Show,
) -> Self {
Self {
voxygen_i18n,
i18n,
client,
new_notifications,
fonts,
@ -126,7 +126,7 @@ impl<'a> Widget for Popup<'a> {
if s.infos.is_empty() {
s.last_info_update = Instant::now();
}
let text = self.voxygen_i18n.get("hud.waypoint_saved");
let text = self.i18n.get("hud.waypoint_saved");
s.infos.push_back(text.to_string());
});
},

View File

@ -5,9 +5,9 @@ use super::{
};
use crate::{
hud::BuffPosition,
i18n::{list_localizations, LanguageMetadata, VoxygenLocalization},
i18n::{list_localizations, LanguageMetadata, Localization},
render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode},
ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton},
ui::{fonts::Fonts, ImageSlider, ScaleMode, ToggleButton},
window::{FullScreenSettings, FullscreenMode, GameInput},
GlobalState,
};
@ -227,8 +227,8 @@ pub struct SettingsWindow<'a> {
global_state: &'a GlobalState,
show: &'a Show,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
fps: f32,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -239,8 +239,8 @@ impl<'a> SettingsWindow<'a> {
global_state: &'a GlobalState,
show: &'a Show,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
fps: f32,
) -> Self {
Self {
@ -2317,7 +2317,6 @@ impl<'a> Widget for SettingsWindow<'a> {
.global_state
.window
.window()
.window()
.current_monitor()
.unwrap()
.video_modes()

View File

@ -6,9 +6,9 @@ use super::{
STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR,
};
use crate::{
i18n::VoxygenLocalization,
i18n::Localization,
ui::{
fonts::ConrodVoxygenFonts,
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, Tooltip, TooltipManager, Tooltipable,
},
@ -121,7 +121,7 @@ pub struct Skillbar<'a> {
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
stats: &'a Stats,
health: &'a Health,
@ -133,7 +133,7 @@ pub struct Skillbar<'a> {
hotbar: &'a hotbar::State,
tooltip_manager: &'a mut TooltipManager,
slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
pulse: f32,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -146,7 +146,7 @@ impl<'a> Skillbar<'a> {
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a ConrodVoxygenFonts,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
stats: &'a Stats,
health: &'a Health,
@ -159,7 +159,7 @@ impl<'a> Skillbar<'a> {
hotbar: &'a hotbar::State,
tooltip_manager: &'a mut TooltipManager,
slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
localized_strings: &'a Localization,
show: &'a Show,
) -> Self {
Self {

View File

@ -4,8 +4,8 @@ use super::{
};
use crate::{
i18n::VoxygenLocalization,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
i18n::Localization,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
};
use client::{self, Client};
use common::{comp::group, sync::Uid};
@ -66,8 +66,8 @@ pub struct Social<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
selected_entity: Option<(specs::Entity, Instant)>,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
@ -82,8 +82,8 @@ impl<'a> Social<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
selected_entity: Option<(specs::Entity, Instant)>,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,

View File

@ -1,5 +1,5 @@
use super::{img_ids::Imgs, Show, TEXT_COLOR, UI_MAIN};
use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts};
use crate::{i18n::Localization, ui::fonts::Fonts};
use conrod_core::{
color,
widget::{self, Button, Image, Rectangle, Text},
@ -24,8 +24,8 @@ pub struct Spell<'a> {
_client: &'a Client,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -36,8 +36,8 @@ impl<'a> Spell<'a> {
show: &'a Show,
_client: &'a Client,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
fonts: &'a Fonts,
localized_strings: &'a Localization,
) -> Self {
Self {
_show: show,

View File

@ -44,11 +44,11 @@ impl Font {
}
/// Store font metadata
pub type VoxygenFonts = HashMap<String, Font>;
pub type Fonts = HashMap<String, Font>;
/// Store internationalization data
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct VoxygenLocalization {
pub struct Localization {
/// A map storing the localized texts
///
/// Localized content can be accessed using a String key.
@ -64,12 +64,12 @@ pub struct VoxygenLocalization {
pub convert_utf8_to_ascii: bool,
/// Font configuration is stored here
pub fonts: VoxygenFonts,
pub fonts: Fonts,
pub metadata: LanguageMetadata,
}
impl VoxygenLocalization {
impl Localization {
/// Get a localized text from the given key
///
/// If the key is not present in the localization object
@ -97,7 +97,7 @@ impl VoxygenLocalization {
/// Return the missing keys compared to the reference language
pub fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
let reference_localization =
VoxygenLocalization::load_expect(i18n_asset_key(REFERENCE_LANG).as_ref());
Localization::load_expect(i18n_asset_key(REFERENCE_LANG).as_ref());
let reference_string_keys: HashSet<_> =
reference_localization.string_map.keys().cloned().collect();
@ -136,14 +136,14 @@ impl VoxygenLocalization {
}
}
impl Asset for VoxygenLocalization {
impl Asset for Localization {
const ENDINGS: &'static [&'static str] = &["ron"];
/// Load the translations located in the input buffer and convert them
/// into a `VoxygenLocalization` object.
/// into a `Localization` object.
#[allow(clippy::into_iter_on_ref)] // TODO: Pending review in #587
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
let mut asked_localization: VoxygenLocalization =
let mut asked_localization: Localization =
from_reader(buf_reader).map_err(assets::Error::parse_error)?;
// Update the text if UTF-8 to ASCII conversion is enabled
@ -163,10 +163,10 @@ impl Asset for VoxygenLocalization {
}
}
/// Load all the available languages located in the Voxygen asset directory
/// Load all the available languages located in the voxygen asset directory
pub fn list_localizations() -> Vec<LanguageMetadata> {
let voxygen_locales_assets = "voxygen.i18n.*";
let lang_list = VoxygenLocalization::load_glob(voxygen_locales_assets).unwrap();
let lang_list = Localization::load_glob(voxygen_locales_assets).unwrap();
lang_list.iter().map(|e| (*e).metadata.clone()).collect()
}
@ -175,7 +175,7 @@ pub fn i18n_asset_key(language_id: &str) -> String { "voxygen.i18n.".to_string()
#[cfg(test)]
mod tests {
use super::VoxygenLocalization;
use super::Localization;
use git2::Repository;
use ron::de::{from_bytes, from_reader};
use std::{
@ -248,7 +248,7 @@ mod tests {
fn generate_key_version<'a>(
repo: &'a git2::Repository,
localization: &VoxygenLocalization,
localization: &Localization,
path: &std::path::Path,
file_blob: &git2::Blob,
) -> HashMap<String, LocalizationEntryState> {
@ -348,7 +348,7 @@ mod tests {
);
for path in i18n_files {
let f = fs::File::open(&path).expect("Failed opening file");
let _: VoxygenLocalization = match from_reader(f) {
let _: Localization = match from_reader(f) {
Ok(v) => v,
Err(e) => {
panic!(
@ -387,7 +387,7 @@ mod tests {
// Read HEAD for the reference language file
let i18n_en_blob = read_file_from_path(&repo, &head_ref, &en_i18n_path);
let loc: VoxygenLocalization = from_bytes(i18n_en_blob.content())
let loc: Localization = from_bytes(i18n_en_blob.content())
.expect("Expect to parse reference i18n RON file, can't proceed without it");
let i18n_references: HashMap<String, LocalizationEntryState> =
generate_key_version(&repo, &loc, &en_i18n_path, &i18n_en_blob);
@ -406,7 +406,7 @@ mod tests {
// Find the localization entry state
let current_blob = read_file_from_path(&repo, &head_ref, &relfile);
let current_loc: VoxygenLocalization = match from_bytes(current_blob.content()) {
let current_loc: Localization = match from_bytes(current_blob.content()) {
Ok(v) => v,
Err(e) => {
eprintln!(

View File

@ -5,7 +5,7 @@
use veloren_voxygen::{
audio::{self, AudioFrontend},
i18n::{self, i18n_asset_key, VoxygenLocalization},
i18n::{self, i18n_asset_key, Localization},
logging,
profile::Profile,
run,
@ -157,7 +157,7 @@ fn main() {
let profile = Profile::load();
let mut localization_watcher = watch::ReloadIndicator::new();
let localized_strings = VoxygenLocalization::load_watched(
let localized_strings = Localization::load_watched(
&i18n_asset_key(&settings.language.selected_language),
&mut localization_watcher,
)
@ -169,7 +169,7 @@ fn main() {
"Impossible to load language: change to the default language (English) instead.",
);
settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
VoxygenLocalization::load_watched(
Localization::load_watched(
&i18n_asset_key(&settings.language.selected_language),
&mut localization_watcher,
)

View File

@ -1,7 +1,7 @@
mod ui;
use crate::{
i18n::{i18n_asset_key, VoxygenLocalization},
i18n::{i18n_asset_key, Localization},
render::Renderer,
scene::simple::{self as scene, Scene},
session::SessionState,
@ -37,19 +37,22 @@ impl CharSelectionState {
}
}
fn get_humanoid_body(&self) -> Option<comp::humanoid::Body> {
self.char_selection_ui
.get_character_list()
.and_then(|data| {
if let Some(character) = data.get(self.char_selection_ui.selected_character) {
match character.body {
fn get_humanoid_body_loadout<'a>(
char_selection_ui: &'a CharSelectionUi,
client: &'a Client,
) -> (Option<comp::humanoid::Body>, Option<&'a comp::Loadout>) {
char_selection_ui
.display_body_loadout(&client.character_list.characters)
.map(|(body, loadout)| {
(
match body {
comp::Body::Humanoid(body) => Some(body),
_ => None,
}
} else {
None
}
},
Some(loadout),
)
})
.unwrap_or_default()
}
}
@ -93,39 +96,34 @@ impl PlayState for CharSelectionState {
return PlayStateResult::Pop;
},
ui::Event::AddCharacter { alias, tool, body } => {
self.client.borrow_mut().create_character(alias, tool, body);
self.client
.borrow_mut()
.create_character(alias, Some(tool), body);
},
ui::Event::DeleteCharacter(character_id) => {
self.client.borrow_mut().delete_character(character_id);
},
ui::Event::Play => {
let char_data = self
.char_selection_ui
.get_character_list()
.expect("Character data is required to play");
if let Some(selected_character) =
char_data.get(self.char_selection_ui.selected_character)
{
if let Some(character_id) = selected_character.character.id {
ui::Event::Play(character_id) => {
self.client.borrow_mut().request_character(character_id);
}
}
return PlayStateResult::Switch(Box::new(SessionState::new(
global_state,
Rc::clone(&self.client),
)));
},
ui::Event::ClearCharacterListError => {
self.client.borrow_mut().character_list.error = None;
},
}
}
let humanoid_body = self.get_humanoid_body();
let loadout = self.char_selection_ui.get_loadout();
// Maintain the scene.
{
let client = self.client.borrow();
let (humanoid_body, loadout) =
Self::get_humanoid_body_loadout(&self.char_selection_ui, &client);
// Maintain the scene.
let scene_data = scene::SceneData {
time: client.state().get_time(),
delta_time: client.state().ecs().read_resource::<DeltaTime>().0,
@ -141,15 +139,13 @@ impl PlayState for CharSelectionState {
.figure_lod_render_distance
as f32,
};
self.scene.maintain(
global_state.window.renderer_mut(),
scene_data,
loadout.as_ref(),
);
self.scene
.maintain(global_state.window.renderer_mut(), scene_data, loadout);
}
// Tick the client (currently only to keep the connection alive).
let localized_strings = VoxygenLocalization::load_expect(&i18n_asset_key(
let localized_strings = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
@ -199,19 +195,15 @@ impl PlayState for CharSelectionState {
fn name(&self) -> &'static str { "Title" }
fn render(&mut self, renderer: &mut Renderer, _: &Settings) {
let humanoid_body = self.get_humanoid_body();
let loadout = self.char_selection_ui.get_loadout();
let client = self.client.borrow();
let (humanoid_body, loadout) =
Self::get_humanoid_body_loadout(&self.char_selection_ui, &client);
// Render the scene.
self.scene.render(
renderer,
self.client.borrow().get_tick(),
humanoid_body,
loadout.as_ref(),
);
self.scene
.render(renderer, client.get_tick(), humanoid_body, loadout);
// Draw the UI to the screen.
self.char_selection_ui
.render(renderer, self.scene.globals());
self.char_selection_ui.render(renderer);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,15 @@ use super::char_selection::CharSelectionState;
#[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer;
use crate::{
render::Renderer, settings::Settings, window::Event, Direction, GlobalState, PlayState,
PlayStateResult,
i18n::{i18n_asset_key, Localization},
render::Renderer,
settings::Settings,
window::Event,
Direction, GlobalState, PlayState, PlayStateResult,
};
use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
use common::{assets::Asset, comp, span};
use tracing::{error, warn};
use tracing::error;
use ui::{Event as MainMenuEvent, MainMenuUi};
pub struct MainMenuState {
@ -47,7 +50,7 @@ impl PlayState for MainMenuState {
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
span!(_guard, "tick", "<MainMenuState as PlayState>::tick");
let localized_strings = crate::i18n::VoxygenLocalization::load_expect(
let mut localized_strings = crate::i18n::Localization::load_expect(
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
);
@ -84,7 +87,7 @@ impl PlayState for MainMenuState {
match event {
Event::Close => return PlayStateResult::Shutdown,
// Pass events to ui.
Event::Ui(event) => {
Event::IcedUi(event) => {
self.main_menu_ui.handle_event(event);
},
// Ignore all other events.
@ -219,6 +222,14 @@ impl PlayState for MainMenuState {
password,
server_address,
} => {
let mut net_settings = &mut global_state.settings.networking;
net_settings.username = username.clone();
net_settings.default_server = server_address.clone();
if !net_settings.servers.contains(&server_address) {
net_settings.servers.push(server_address.clone());
}
global_state.settings.save_to_file_warn();
attempt_login(
&mut global_state.settings,
&mut global_state.info_message,
@ -240,15 +251,25 @@ impl PlayState for MainMenuState {
self.client_init = None;
self.main_menu_ui.cancel_connection();
},
MainMenuEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
localized_strings = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
localized_strings.log_missing_entries();
self.main_menu_ui
.update_language(std::sync::Arc::clone(&localized_strings));
},
#[cfg(feature = "singleplayer")]
MainMenuEvent::StartSingleplayer => {
let singleplayer = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
global_state.singleplayer = Some(singleplayer);
},
MainMenuEvent::Settings => {}, // TODO
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
/*MainMenuEvent::DisclaimerClosed => {
// Note: Keeping in case we re-add the disclaimer
/*MainMenuEvent::DisclaimerAccepted => {
global_state.settings.show_disclaimer = false
},*/
MainMenuEvent::AuthServerTrust(auth_server, trust) => {
@ -291,15 +312,6 @@ fn attempt_login(
server_port: u16,
client_init: &mut Option<ClientInit>,
) {
let mut net_settings = &mut settings.networking;
net_settings.username = username.clone();
if !net_settings.servers.contains(&server_address) {
net_settings.servers.push(server_address.clone());
}
if let Err(e) = settings.save_to_file() {
warn!(?e, "Failed to save settings");
}
if comp::Player::alias_is_valid(&username) {
// Don't try to connect if there is already a connection in progress.
if client_init.is_none() {

View File

@ -1,907 +0,0 @@
use crate::{
i18n::{i18n_asset_key, VoxygenLocalization},
render::Renderer,
ui::{
self,
fonts::ConrodVoxygenFonts,
img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic},
Graphic, ImageFrame, Tooltip, Ui,
},
GlobalState,
};
use common::assets::Asset;
use conrod_core::{
color,
color::TRANSPARENT,
position::Relative,
widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox},
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
};
use image::DynamicImage;
use rand::{seq::SliceRandom, thread_rng, Rng};
use std::time::Duration;
const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9);
// UI Color-Theme
/*const UI_MAIN: Color = Color::Rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue
const UI_HIGHLIGHT_0: Color = Color::Rgba(0.79, 1.09, 1.09, 1.0);*/
widget_ids! {
struct Ids {
// Background and logo
bg,
v_logo,
alpha_version,
alpha_text,
banner,
banner_top,
gears,
// Disclaimer
//disc_window,
//disc_text_1,
//disc_text_2,
//disc_button,
//disc_scrollbar,
// Login, Singleplayer
login_button,
login_text,
login_error,
login_error_bg,
address_text,
address_bg,
address_field,
username_text,
username_bg,
username_field,
password_text,
password_bg,
password_field,
singleplayer_button,
singleplayer_text,
usrnm_bg,
srvr_bg,
passwd_bg,
// Server list
servers_button,
servers_frame,
servers_text,
servers_close,
// Buttons
settings_button,
quit_button,
// Error
error_frame,
button_ok,
version,
// Info Window
info_frame,
info_text,
info_bottom,
// Auth Trust Prompt
button_add_auth_trust,
// Loading Screen Tips
tip_txt_bg,
tip_txt,
// Loading Screen Artwork
mid,
left,
right,
}
}
image_ids! {
struct Imgs {
<VoxelGraphic>
v_logo: "voxygen.element.v_logo",
info_frame: "voxygen.element.frames.info_frame_2",
<ImageGraphic>
bg: "voxygen.background.bg_main",
banner_top: "voxygen.element.frames.banner_top",
banner: "voxygen.element.frames.banner",
banner_bottom: "voxygen.element.frames.banner_bottom",
button: "voxygen.element.buttons.button",
button_hover: "voxygen.element.buttons.button_hover",
button_press: "voxygen.element.buttons.button_press",
input_bg: "voxygen.element.misc_bg.textbox_mid",
//disclaimer: "voxygen.element.frames.disclaimer",
loading_art: "voxygen.element.frames.loading_screen.loading_bg",
loading_art_l: "voxygen.element.frames.loading_screen.loading_bg_l",
loading_art_r: "voxygen.element.frames.loading_screen.loading_bg_r",
// Animation
f1: "voxygen.element.animation.gears.1",
f2: "voxygen.element.animation.gears.2",
f3: "voxygen.element.animation.gears.3",
f4: "voxygen.element.animation.gears.4",
f5: "voxygen.element.animation.gears.5",
<BlankGraphic>
nothing: (),
}
}
rotation_image_ids! {
pub struct ImgsRot {
<ImageGraphic>
// Tooltip Test
tt_side: "voxygen/element/frames/tt_test_edge",
tt_corner: "voxygen/element/frames/tt_test_corner_tr",
}
}
pub enum Event {
LoginAttempt {
username: String,
password: String,
server_address: String,
},
CancelLoginAttempt,
#[cfg(feature = "singleplayer")]
StartSingleplayer,
Quit,
Settings,
//DisclaimerClosed,
AuthServerTrust(String, bool),
}
pub enum PopupType {
Error,
ConnectionInfo,
AuthTrustPrompt(String),
}
pub struct PopupData {
msg: String,
popup_type: PopupType,
}
pub struct MainMenuUi {
ui: Ui,
ids: Ids,
imgs: Imgs,
rot_imgs: ImgsRot,
username: String,
password: String,
server_address: String,
popup: Option<PopupData>,
connecting: Option<std::time::Instant>,
connect: bool,
show_servers: bool,
//show_disclaimer: bool,
time: f32,
anim_timer: f32,
bg_img_id: conrod_core::image::Id,
voxygen_i18n: std::sync::Arc<VoxygenLocalization>,
fonts: ConrodVoxygenFonts,
tip_no: u16,
}
impl<'a> MainMenuUi {
pub fn new(global_state: &mut GlobalState) -> Self {
let window = &mut global_state.window;
let networking = &global_state.settings.networking;
let gameplay = &global_state.settings.gameplay;
// Randomly loaded background images
let bg_imgs = [
"voxygen.background.bg_1",
"voxygen.background.bg_2",
"voxygen.background.bg_3",
"voxygen.background.bg_4",
"voxygen.background.bg_5",
"voxygen.background.bg_6",
"voxygen.background.bg_7",
"voxygen.background.bg_8",
"voxygen.background.bg_9",
//"voxygen.background.bg_10",
"voxygen.background.bg_11",
//"voxygen.background.bg_12",
"voxygen.background.bg_13",
//"voxygen.background.bg_14",
"voxygen.background.bg_15",
"voxygen.background.bg_16",
];
let mut rng = thread_rng();
let mut ui = Ui::new(window).unwrap();
ui.set_scaling_mode(gameplay.ui_scale);
// Generate ids
let ids = Ids::new(ui.id_generator());
// Load images
let imgs = Imgs::load(&mut ui).expect("Failed to load images");
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load images!");
let bg_img_id = ui.add_graphic(Graphic::Image(
DynamicImage::load_expect(bg_imgs.choose(&mut rng).unwrap()),
None,
));
//let chosen_tip = *tips.choose(&mut rng).unwrap();
// Load language
let voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// Load fonts.
let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui)
.expect("Impossible to load fonts!");
Self {
ui,
ids,
imgs,
rot_imgs,
username: networking.username.clone(),
password: "".to_owned(),
server_address: networking
.servers
.get(networking.default_server)
.cloned()
.unwrap_or_default(),
popup: None,
connecting: None,
show_servers: false,
connect: false,
time: 0.0,
anim_timer: 0.0,
//show_disclaimer: global_state.settings.show_disclaimer,
bg_img_id,
voxygen_i18n,
fonts,
tip_no: 0,
}
}
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
#[allow(clippy::op_ref)] // TODO: Pending review in #587
#[allow(clippy::toplevel_ref_arg)] // TODO: Pending review in #587
fn update_layout(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
let mut events = Vec::new();
self.time = self.time + dt.as_secs_f32();
let fade_msg = (self.time * 2.0).sin() * 0.5 + 0.51;
let (ref mut ui_widgets, ref mut _tooltip_manager) = self.ui.set_widgets();
let tip_msg = format!(
"{} {}",
&self.voxygen_i18n.get("main.tip"),
&self.voxygen_i18n.get_variation("loading.tips", self.tip_no),
);
let tip_show = global_state.settings.gameplay.loading_tips;
let mut rng = thread_rng();
let version = common::util::DISPLAY_VERSION_LONG.clone();
let scale = 0.8;
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
const TEXT_COLOR_2: Color = Color::Rgba(1.0, 1.0, 1.0, 0.2);
const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
//const INACTIVE: Color = Color::Rgba(0.47, 0.47, 0.47, 0.47);
let intro_text = &self.voxygen_i18n.get("main.login_process");
// Tooltip
let _tooltip = Tooltip::new({
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
})
.title_font_size(self.fonts.cyri.scale(15))
.desc_font_size(self.fonts.cyri.scale(10))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR_2);
// Background image, Veloren logo, Alpha-Version Label
Image::new(if self.connect {
self.bg_img_id
} else {
self.imgs.bg
})
.middle_of(ui_widgets.window)
.set(self.ids.bg, ui_widgets);
if self.connect {
// Artwork
Image::new(self.imgs.loading_art)
.h(100.0)
.w_of(self.ids.bg)
.mid_bottom_of(self.ids.bg)
.set(self.ids.mid, ui_widgets);
Image::new(self.imgs.loading_art_l)
.w_h(12.0, 10.0)
.top_left_with_margins_on(self.ids.mid, 2.0, 0.0)
.set(self.ids.left, ui_widgets);
Image::new(self.imgs.loading_art_r)
.w_h(12.0, 10.0)
.top_right_with_margins_on(self.ids.mid, 2.0, 0.0)
.set(self.ids.right, ui_widgets);
// Gears Animation
self.anim_timer = (self.anim_timer + dt.as_secs_f32()) * 1.05; // Linear time function with Anim-Speed Factor
if self.anim_timer >= 4.0 {
self.anim_timer = 0.0 // Reset timer at last frame to loop
};
Image::new(match self.anim_timer.round() as i32 {
0 => self.imgs.f1,
1 => self.imgs.f2,
2 => self.imgs.f3,
3 => self.imgs.f4,
_ => self.imgs.f5,
})
.w_h(74.0, 62.0)
.bottom_right_with_margins_on(self.ids.mid, 10.0, 10.0)
.set(self.ids.gears, ui_widgets);
if tip_show {
// Tips
Text::new(&tip_msg)
.color(TEXT_BG)
.mid_bottom_with_margin_on(self.ids.mid, 60.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.tip_txt_bg, ui_widgets);
Text::new(&tip_msg)
.color(TEXT_COLOR)
.bottom_left_with_margins_on(self.ids.tip_txt_bg, 2.0, 2.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.tip_txt, ui_widgets);
};
};
// Version displayed top right corner
let pos = if self.connect { 5.0 } else { 98.0 };
Text::new(&version)
.color(TEXT_COLOR)
.top_right_with_margins_on(ui_widgets.window, pos * scale, 10.0 * scale)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.version, ui_widgets);
// Alpha Disclaimer
Text::new(&format!(
"Veloren {}",
common::util::DISPLAY_VERSION.as_str()
))
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(10))
.color(TEXT_COLOR)
.mid_top_with_margin_on(ui_widgets.window, 2.0)
.set(self.ids.alpha_text, ui_widgets);
// Popup (Error/Info/AuthTrustPrompt)
let mut change_popup = None;
if let Some(PopupData { msg, popup_type }) = &self.popup {
let text = Text::new(msg)
.rgba(
1.0,
1.0,
1.0,
if let PopupType::ConnectionInfo = popup_type {
fade_msg
} else {
1.0
},
)
.font_id(self.fonts.cyri.conrod_id);
let (frame_w, frame_h) = if let PopupType::AuthTrustPrompt(_) = popup_type {
(65.0 * 8.0, 370.0)
} else {
(65.0 * 6.0, 140.0)
};
let error_bg = Rectangle::fill_with([frame_w, frame_h], color::TRANSPARENT)
.rgba(0.1, 0.1, 0.1, if self.connect { 0.0 } else { 1.0 })
.parent(ui_widgets.window);
if let PopupType::AuthTrustPrompt(_) = popup_type {
error_bg.middle_of(ui_widgets.window)
} else {
error_bg.up_from(self.ids.banner_top, 15.0)
}
.set(self.ids.login_error_bg, ui_widgets);
Image::new(self.imgs.info_frame)
.w_h(frame_w, frame_h)
.color(Some(Color::Rgba(
1.0,
1.0,
1.0,
if let PopupType::ConnectionInfo = popup_type {
0.0
} else {
1.0
},
)))
.middle_of(self.ids.login_error_bg)
.set(self.ids.error_frame, ui_widgets);
if let PopupType::ConnectionInfo = popup_type {
/*text.mid_top_with_margin_on(self.ids.error_frame, 10.0)
.font_id(self.fonts.cyri.conrod_id)
.bottom_left_with_margins_on(self.ids.bg, 30.0, 95.0)
.font_size(self.fonts.cyri.scale(35))
.set(self.ids.login_error, ui_widgets);*/
} else {
text.mid_top_with_margin_on(self.ids.error_frame, 10.0)
.w(frame_w - 10.0 * 2.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.login_error, ui_widgets);
};
if Button::image(self.imgs.button)
.w_h(100.0, 30.0)
.mid_bottom_with_margin_on(
if let PopupType::ConnectionInfo = popup_type {
ui_widgets.window
} else {
self.ids.login_error_bg
},
10.0,
)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label_y(Relative::Scalar(2.0))
.label(match popup_type {
PopupType::Error => self.voxygen_i18n.get("common.okay"),
PopupType::ConnectionInfo => self.voxygen_i18n.get("common.cancel"),
PopupType::AuthTrustPrompt(_) => self.voxygen_i18n.get("common.cancel"),
})
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(self.fonts.cyri.scale(15))
.label_color(TEXT_COLOR)
.set(self.ids.button_ok, ui_widgets)
.was_clicked()
{
match &popup_type {
PopupType::Error => (),
PopupType::ConnectionInfo => {
events.push(Event::CancelLoginAttempt);
},
PopupType::AuthTrustPrompt(auth_server) => {
events.push(Event::AuthServerTrust(auth_server.clone(), false));
},
};
change_popup = Some(None);
}
if let PopupType::AuthTrustPrompt(auth_server) = popup_type {
if Button::image(self.imgs.button)
.w_h(100.0, 30.0)
.right_from(self.ids.button_ok, 10.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label_y(Relative::Scalar(2.0))
.label("Add") // TODO: localize
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(self.fonts.cyri.scale(15))
.label_color(TEXT_COLOR)
.set(self.ids.button_add_auth_trust, ui_widgets)
.was_clicked()
{
events.push(Event::AuthServerTrust(auth_server.clone(), true));
change_popup = Some(Some(PopupData {
msg: self.voxygen_i18n.get("main.connecting").into(),
popup_type: PopupType::ConnectionInfo,
}));
}
}
}
if let Some(p) = change_popup {
self.popup = p;
}
if !self.connect {
Image::new(self.imgs.banner)
.w_h(65.0 * 6.0 * scale, 100.0 * 6.0 * scale)
.middle_of(self.ids.bg)
.color(Some(Color::Rgba(0.0, 0.0, 0.0, 0.0)))
.set(self.ids.banner, ui_widgets);
Image::new(self.imgs.banner_top)
.w_h(70.0 * 6.0 * scale, 34.0 * scale)
.mid_top_with_margin_on(self.ids.banner, -34.0)
.color(Some(Color::Rgba(0.0, 0.0, 0.0, 0.0)))
.set(self.ids.banner_top, ui_widgets);
// Logo
Image::new(self.imgs.v_logo)
.w_h(123.0 * 2.5 * scale, 35.0 * 2.5 * scale)
.top_right_with_margins_on(self.ids.bg, 10.0, 10.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.95)))
.set(self.ids.v_logo, ui_widgets);
/*if self.show_disclaimer {
Image::new(self.imgs.disclaimer)
.w_h(1800.0, 800.0)
.middle_of(ui_widgets.window)
.scroll_kids()
.scroll_kids_vertically()
.set(self.ids.disc_window, ui_widgets);
Text::new(&self.voxygen_i18n.get("common.disclaimer"))
.top_left_with_margins_on(self.ids.disc_window, 30.0, 40.0)
.font_size(self.fonts.cyri.scale(35))
.font_id(self.fonts.alkhemi.conrod_id)
.color(TEXT_COLOR)
.set(self.ids.disc_text_1, ui_widgets);
Text::new(&self.voxygen_i18n.get("main.notice"))
.top_left_with_margins_on(self.ids.disc_window, 110.0, 40.0)
.font_size(self.fonts.cyri.scale(26))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(self.ids.disc_text_2, ui_widgets);
if Button::image(self.imgs.button)
.w_h(300.0, 50.0)
.mid_bottom_with_margin_on(self.ids.disc_window, 30.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label_y(Relative::Scalar(2.0))
.label(&self.voxygen_i18n.get("common.accept"))
.label_font_size(self.fonts.cyri.scale(22))
.label_color(TEXT_COLOR)
.label_font_id(self.fonts.cyri.conrod_id)
.set(self.ids.disc_button, ui_widgets)
.was_clicked()
{
self.show_disclaimer = false;
events.push(Event::DisclaimerClosed);
}
} else {*/
// TODO: Don't use macros for this?
// Input fields
// Used when the login button is pressed, or enter is pressed within input field
macro_rules! login {
() => {
self.connect = true;
self.connecting = Some(std::time::Instant::now());
self.popup = Some(PopupData {
msg: [self.voxygen_i18n.get("main.connecting"), "..."].concat(),
popup_type: PopupType::ConnectionInfo,
});
events.push(Event::LoginAttempt {
username: self.username.clone(),
password: self.password.clone(),
server_address: self.server_address.clone(),
});
};
}
// Info Window
Rectangle::fill_with([550.0 * scale, 250.0 * scale], COL1)
.top_left_with_margins_on(ui_widgets.window, 40.0 * scale, 40.0 * scale)
.color(Color::Rgba(0.0, 0.0, 0.0, 0.80))
.set(self.ids.info_frame, ui_widgets);
Image::new(self.imgs.banner_bottom)
.mid_bottom_with_margin_on(self.ids.info_frame, -50.0 * scale)
.w_h(550.0 * scale, 50.0 * scale)
.color(Some(Color::Rgba(0.0, 0.0, 0.0, 0.80)))
.set(self.ids.info_bottom, ui_widgets);
Text::new(intro_text)
.top_left_with_margins_on(self.ids.info_frame, 15.0 * scale, 15.0 * scale)
.font_size(self.fonts.cyri.scale(16))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(self.ids.info_text, ui_widgets);
// Singleplayer
// Used when the singleplayer button is pressed
#[cfg(feature = "singleplayer")]
macro_rules! singleplayer {
() => {
events.push(Event::StartSingleplayer);
self.connect = true;
self.connecting = Some(std::time::Instant::now());
self.popup = Some(PopupData {
msg: [self.voxygen_i18n.get(""), ""].concat(),
popup_type: PopupType::ConnectionInfo,
});
};
}
// Username
Rectangle::fill_with(
[320.0 * scale, 50.0 * scale],
color::rgba(0.0, 0.0, 0.0, 0.0),
)
.mid_top_with_margin_on(self.ids.banner_top, 150.0)
.set(self.ids.usrnm_bg, ui_widgets);
Image::new(self.imgs.input_bg)
.w_h(338.0 * scale, 50.0 * scale)
.middle_of(self.ids.usrnm_bg)
.set(self.ids.username_bg, ui_widgets);
for event in TextBox::new(&self.username)
.w_h(290.0* scale, 30.0* scale)
.mid_bottom_with_margin_on(self.ids.username_bg, 14.0* scale)
.font_size(self.fonts.cyri.scale(18))
.font_id(self.fonts.cyri.conrod_id)
.text_color(TEXT_COLOR)
// transparent background
.color(TRANSPARENT)
.border_color(TRANSPARENT)
.set(self.ids.username_field, ui_widgets)
{
match event {
TextBoxEvent::Update(username) => {
// Note: TextBox limits the input string length to what fits in it
self.username = username.to_string();
},
TextBoxEvent::Enter => {
login!();
},
}
}
// Password
Rectangle::fill_with(
[320.0 * scale, 50.0 * scale],
color::rgba(0.0, 0.0, 0.0, 0.0),
)
.down_from(self.ids.usrnm_bg, 10.0 * scale)
.set(self.ids.passwd_bg, ui_widgets);
Image::new(self.imgs.input_bg)
.w_h(338.0 * scale, 50.0 * scale)
.middle_of(self.ids.passwd_bg)
.set(self.ids.password_bg, ui_widgets);
for event in TextBox::new(&self.password)
.w_h(290.0 * scale, 30.0* scale)
.mid_bottom_with_margin_on(self.ids.password_bg, 10.0* scale)
// The text is smaller to allow longer passwords, conrod limits text length
// Basically the lower the scale of the font, the smaller and more characters we can fit
// At the time of this commit change, scale of 10 should fit 34 characters
.font_size(self.fonts.cyri.scale(10))
.font_id(self.fonts.cyri.conrod_id)
.text_color(TEXT_COLOR)
// transparent background
.color(TRANSPARENT)
.border_color(TRANSPARENT)
.hide_text("*")
.set(self.ids.password_field, ui_widgets)
{
match event {
TextBoxEvent::Update(password) => {
// Note: TextBox limits the input string length to what fits in it
self.password = password;
},
TextBoxEvent::Enter => {
self.password.pop();
login!();
},
}
}
if self.show_servers {
Image::new(self.imgs.info_frame)
.mid_top_with_margin_on(self.ids.username_bg, -320.0)
.w_h(400.0, 300.0)
.set(self.ids.servers_frame, ui_widgets);
let ref mut net_settings = global_state.settings.networking;
// TODO: Draw scroll bar or remove it.
let (mut items, _scrollbar) = List::flow_down(net_settings.servers.len())
.top_left_with_margins_on(self.ids.servers_frame, 0.0, 5.0)
.w_h(400.0, 300.0)
.scrollbar_next_to()
.scrollbar_thickness(18.0)
.scrollbar_color(TEXT_COLOR)
.set(self.ids.servers_text, ui_widgets);
while let Some(item) = items.next(ui_widgets) {
let mut text = "".to_string();
if &net_settings.servers[item.i] == &self.server_address {
text.push_str("-> ")
} else {
text.push_str(" ")
}
text.push_str(&net_settings.servers[item.i]);
if item
.set(
Button::image(self.imgs.nothing)
.w_h(100.0, 50.0)
.mid_top_with_margin_on(self.ids.servers_frame, 10.0)
//.hover_image(self.imgs.button_hover)
//.press_image(self.imgs.button_press)
.label_y(Relative::Scalar(2.0))
.label(&text)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR),
ui_widgets,
)
.was_clicked()
{
self.server_address = net_settings.servers[item.i].clone();
net_settings.default_server = item.i;
}
}
if Button::image(self.imgs.button)
.w_h(200.0, 53.0)
.mid_bottom_with_margin_on(self.ids.servers_frame, 5.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label_y(Relative::Scalar(2.0))
.label(&self.voxygen_i18n.get("common.close"))
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR)
.set(self.ids.servers_close, ui_widgets)
.was_clicked()
{
self.show_servers = false
};
}
// Server address
Rectangle::fill_with(
[320.0 * scale, 50.0 * scale],
color::rgba(0.0, 0.0, 0.0, 0.0),
)
.down_from(self.ids.passwd_bg, 8.0 * scale)
.set(self.ids.srvr_bg, ui_widgets);
Image::new(self.imgs.input_bg)
.w_h(338.0 * scale, 50.0 * scale)
.middle_of(self.ids.srvr_bg)
.set(self.ids.address_bg, ui_widgets);
for event in TextBox::new(&self.server_address)
.w_h(290.0*scale, 30.0*scale)
.mid_top_with_margin_on(self.ids.address_bg, 8.0*scale)
.font_size(self.fonts.cyri.scale(18))
.font_id(self.fonts.cyri.conrod_id)
.text_color(TEXT_COLOR)
// transparent background
.color(TRANSPARENT)
.border_color(TRANSPARENT)
.set(self.ids.address_field, ui_widgets)
{
match event {
TextBoxEvent::Update(server_address) => {
self.server_address = server_address.to_string();
},
TextBoxEvent::Enter => {
login!();
},
}
}
// Login button
if Button::image(self.imgs.button)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.w_h(258.0*scale, 55.0*scale)
.down_from(self.ids.address_bg, 20.0*scale)
.align_middle_x_of(self.ids.address_bg)
.label(&self.voxygen_i18n.get("common.multiplayer"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR)
.label_font_size(self.fonts.cyri.scale(18))
.label_y(Relative::Scalar(4.0))
/*.with_tooltip(
tooltip_manager,
"Login",
"Click to login with the entered details",
&tooltip,
)
.tooltip_image(self.imgs.v_logo)*/
.set(self.ids.login_button, ui_widgets)
.was_clicked()
{
self.tip_no = rng.gen();
login!();
}
// Singleplayer button
#[cfg(feature = "singleplayer")]
{
if Button::image(self.imgs.button)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.w_h(258.0 * scale, 55.0 * scale)
.down_from(self.ids.login_button, 20.0 * scale)
.align_middle_x_of(self.ids.address_bg)
.label(&self.voxygen_i18n.get("common.singleplayer"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR)
.label_font_size(self.fonts.cyri.scale(18))
.label_y(Relative::Scalar(4.0))
.label_x(Relative::Scalar(2.0))
.set(self.ids.singleplayer_button, ui_widgets)
.was_clicked()
{
self.tip_no = rng.gen();
singleplayer!();
}
}
// Quit
if Button::image(self.imgs.button)
.w_h(190.0 * scale, 40.0 * scale)
.bottom_left_with_margins_on(ui_widgets.window, 60.0 * scale, 30.0 * scale)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label(&self.voxygen_i18n.get("common.quit"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR)
.label_font_size(self.fonts.cyri.scale(16))
.label_y(Relative::Scalar(3.0))
.set(self.ids.quit_button, ui_widgets)
.was_clicked()
{
events.push(Event::Quit);
}
// Settings
if Button::image(self.imgs.button)
.w_h(190.0*scale, 40.0*scale)
.up_from(self.ids.quit_button, 8.0*scale)
//.hover_image(self.imgs.button_hover)
//.press_image(self.imgs.button_press)
.label(&self.voxygen_i18n.get("common.settings"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR_2)
.label_font_size(self.fonts.cyri.scale(16))
.label_y(Relative::Scalar(3.0))
.set(self.ids.settings_button, ui_widgets)
.was_clicked()
{
events.push(Event::Settings);
}
// Servers
if Button::image(self.imgs.button)
.w_h(190.0 * scale, 40.0 * scale)
.up_from(self.ids.settings_button, 8.0 * scale)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label(&self.voxygen_i18n.get("common.servers"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_color(TEXT_COLOR)
.label_font_size(self.fonts.cyri.scale(16))
.label_y(Relative::Scalar(3.0))
.set(self.ids.servers_button, ui_widgets)
.was_clicked()
{
self.show_servers = !self.show_servers;
};
}
events
}
pub fn auth_trust_prompt(&mut self, auth_server: String) {
self.popup = Some(PopupData {
msg: format!(
"Warning: The server you are trying to connect to has provided this \
authentication server address:\n\n{}\n\nbut it is not in your list of trusted \
authentication servers.\n\nMake sure that you trust this site and owner to not \
try and bruteforce your password!",
&auth_server
),
popup_type: PopupType::AuthTrustPrompt(auth_server),
})
}
pub fn show_info(&mut self, msg: String) {
self.popup = Some(PopupData {
msg,
popup_type: PopupType::Error,
});
self.connecting = None;
self.connect = false;
}
pub fn connected(&mut self) {
self.popup = None;
self.connecting = None;
self.connect = false;
}
pub fn cancel_connection(&mut self) {
self.popup = None;
self.connecting = None;
self.connect = false;
}
pub fn handle_event(&mut self, event: ui::Event) { self.ui.handle_event(event); }
pub fn maintain(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
let events = self.update_layout(global_state, dt);
self.ui.maintain(global_state.window.renderer_mut(), None);
events
}
pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer, None); }
}

View File

@ -0,0 +1,183 @@
use super::{ConnectionState, Imgs, Message};
use crate::{
i18n::Localization,
ui::{
fonts::IcedFonts as Fonts,
ice::{component::neat_button, style, widget::Image, Element},
},
};
use iced::{button, Align, Column, Container, Length, Row, Space, Text};
const GEAR_ANIMATION_SPEED_FACTOR: f64 = 10.0;
/// Connecting screen for the main menu
pub struct Screen {
cancel_button: button::State,
add_button: button::State,
tip_number: u16,
}
impl Screen {
pub fn new() -> Self {
Self {
cancel_button: Default::default(),
add_button: Default::default(),
tip_number: rand::random(),
}
}
pub(super) fn view(
&mut self,
fonts: &Fonts,
imgs: &Imgs,
connection_state: &ConnectionState,
time: f64,
i18n: &Localization,
button_style: style::button::Style,
show_tip: bool,
) -> Element<Message> {
let gear_anim_time = time * GEAR_ANIMATION_SPEED_FACTOR;
// TODO: add built in support for animated images
let gear_anim_image = match (gear_anim_time % 5.0).trunc() as u8 {
0 => imgs.f1,
1 => imgs.f2,
2 => imgs.f3,
3 => imgs.f4,
_ => imgs.f5,
};
let children = match connection_state {
ConnectionState::InProgress => {
let tip = if show_tip {
let tip = format!(
"{} {}",
&i18n.get("main.tip"),
&i18n.get_variation("loading.tips", self.tip_number)
);
Container::new(Text::new(tip).size(fonts.cyri.scale(25)))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.align_y(Align::End)
.into()
} else {
Space::new(Length::Fill, Length::Fill).into()
};
let cancel = Container::new(neat_button(
&mut self.cancel_button,
i18n.get("common.cancel"),
0.7,
button_style,
Some(Message::CancelConnect),
))
.width(Length::Fill)
.height(Length::Units(fonts.cyri.scale(30)))
.center_x()
.padding(3);
let tip_cancel = Column::with_children(vec![tip, cancel.into()])
.width(Length::FillPortion(3))
.align_items(Align::Center)
.spacing(5)
.padding(5);
let gear = Container::new(
Image::new(gear_anim_image)
.width(Length::Units(74))
.height(Length::Units(62)),
)
.width(Length::Fill)
.padding(10)
.align_x(Align::End);
let bottom_content = Row::with_children(vec![
Space::new(Length::Fill, Length::Shrink).into(),
tip_cancel.into(),
gear.into(),
])
.align_items(Align::Center)
.width(Length::Fill);
let left_art = Image::new(imgs.loading_art_l)
.width(Length::Units(12))
.height(Length::Units(12));
let right_art = Image::new(imgs.loading_art_r)
.width(Length::Units(12))
.height(Length::Units(12));
let bottom_bar = Container::new(Row::with_children(vec![
left_art.into(),
bottom_content.into(),
right_art.into(),
]))
.height(Length::Units(85))
.style(style::container::Style::image(imgs.loading_art));
vec![
Space::new(Length::Fill, Length::Fill).into(),
bottom_bar.into(),
]
},
ConnectionState::AuthTrustPrompt { msg, .. } => {
let text = Text::new(msg).size(fonts.cyri.scale(25));
let cancel = neat_button(
&mut self.cancel_button,
i18n.get("common.cancel"),
0.7,
button_style,
Some(Message::TrustPromptCancel),
);
let add = neat_button(
&mut self.add_button,
i18n.get("common.add"),
0.7,
button_style,
Some(Message::TrustPromptAdd),
);
let content = Column::with_children(vec![
text.into(),
Container::new(
Row::with_children(vec![cancel, add])
.spacing(20)
.height(Length::Units(25)),
)
.align_x(Align::End)
.width(Length::Fill)
.into(),
])
.spacing(4)
.max_width(520)
.width(Length::Fill)
.height(Length::Fill);
let prompt_window = Container::new(content)
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.padding(20);
let container = Container::new(prompt_window)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y();
vec![
container.into(),
Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
]
},
};
Column::with_children(children)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}

View File

@ -0,0 +1,82 @@
use super::Message;
use crate::{
i18n::Localization,
ui::{
fonts::IcedFonts as Fonts,
ice::{component::neat_button, style, Element},
},
};
use iced::{button, scrollable, Column, Container, Length, Scrollable, Space};
/// Connecting screen for the main menu
pub struct Screen {
accept_button: button::State,
scroll: scrollable::State,
}
impl Screen {
pub fn new() -> Self {
Self {
accept_button: Default::default(),
scroll: Default::default(),
}
}
pub(super) fn view(
&mut self,
fonts: &Fonts,
i18n: &Localization,
button_style: style::button::Style,
) -> Element<Message> {
Container::new(
Container::new(
Column::with_children(vec![
iced::Text::new(i18n.get("common.disclaimer"))
.font(fonts.alkhemi.id)
.size(fonts.alkhemi.scale(35))
.into(),
Space::new(Length::Fill, Length::Units(20)).into(),
Scrollable::new(&mut self.scroll)
.push(
iced::Text::new(i18n.get("main.notice"))
.font(fonts.cyri.id)
.size(fonts.cyri.scale(23)),
)
.height(Length::FillPortion(1))
.into(),
Container::new(
Container::new(neat_button(
&mut self.accept_button,
i18n.get("common.accept"),
0.7,
button_style,
Some(Message::AcceptDisclaimer),
))
.height(Length::Units(fonts.cyri.scale(50))),
)
.center_x()
.height(Length::Shrink)
.width(Length::Fill)
.into(),
])
.spacing(5)
.padding(20)
.width(Length::Fill)
.height(Length::Fill),
)
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 19, 17, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
),
)
.center_x()
.center_y()
.padding(70)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}

View File

@ -0,0 +1,434 @@
use super::{Imgs, LoginInfo, Message, FILL_FRAC_ONE, FILL_FRAC_TWO};
use crate::{
i18n::Localization,
ui::{
fonts::IcedFonts as Fonts,
ice::{
component::neat_button,
style,
widget::{
compound_graphic::{CompoundGraphic, Graphic},
BackgroundContainer, Image, Padding,
},
Element,
},
},
};
use iced::{
button, scrollable, text_input, Align, Button, Column, Container, Length, Row, Scrollable,
Space, Text, TextInput,
};
use vek::*;
const INPUT_WIDTH: u16 = 230;
const INPUT_TEXT_SIZE: u16 = 20;
/// Login screen for the main menu
pub struct Screen {
quit_button: button::State,
settings_button: button::State,
servers_button: button::State,
language_select_button: button::State,
error_okay_button: button::State,
pub banner: LoginBanner,
language_selection: LanguageSelectBanner,
}
impl Screen {
pub fn new() -> Self {
Self {
servers_button: Default::default(),
settings_button: Default::default(),
quit_button: Default::default(),
language_select_button: Default::default(),
error_okay_button: Default::default(),
banner: LoginBanner::new(),
language_selection: LanguageSelectBanner::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn view(
&mut self,
fonts: &Fonts,
imgs: &Imgs,
login_info: &LoginInfo,
error: Option<&str>,
i18n: &Localization,
is_selecting_language: bool,
selected_language_index: Option<usize>,
language_metadatas: &[crate::i18n::LanguageMetadata],
button_style: style::button::Style,
version: &str,
) -> Element<Message> {
let buttons = Column::with_children(vec![
neat_button(
&mut self.servers_button,
i18n.get("common.servers"),
FILL_FRAC_ONE,
button_style,
Some(Message::ShowServers),
),
neat_button(
&mut self.settings_button,
i18n.get("common.settings"),
FILL_FRAC_ONE,
button_style,
None,
),
neat_button(
&mut self.language_select_button,
i18n.get("common.languages"),
FILL_FRAC_ONE,
button_style,
Some(Message::OpenLanguageMenu),
),
neat_button(
&mut self.quit_button,
i18n.get("common.quit"),
FILL_FRAC_ONE,
button_style,
Some(Message::Quit),
),
])
.width(Length::Fill)
.max_width(100)
.spacing(5);
let buttons = Container::new(buttons)
.width(Length::Fill)
.height(Length::Fill)
.align_y(Align::End);
let intro_text = i18n.get("main.login_process");
let info_window = BackgroundContainer::new(
CompoundGraphic::from_graphics(vec![
Graphic::rect(Rgba::new(0, 0, 0, 240), [500, 300], [0, 0]),
// Note: a way to tell it to keep the height of this one piece constant and
// unstreched would be nice, I suppose we could just break this out into a
// column and use Length::Units
Graphic::image(imgs.banner_gradient_bottom, [500, 50], [0, 300])
.color(Rgba::new(0, 0, 0, 240)),
])
.height(Length::Shrink),
Text::new(intro_text).size(fonts.cyri.scale(18)),
)
.max_width(360)
.padding(Padding::new().horizontal(20).top(10).bottom(60));
let left_column = Column::with_children(vec![info_window.into(), buttons.into()])
.width(Length::Fill)
.height(Length::Fill)
.padding(27)
.into();
let central_content = if let Some(error) = error {
Container::new(
Column::with_children(vec![
Container::new(Text::new(error)).height(Length::Fill).into(),
Container::new(neat_button(
&mut self.error_okay_button,
i18n.get("common.okay"),
FILL_FRAC_ONE,
button_style,
Some(Message::CloseError),
))
.width(Length::Fill)
.height(Length::Units(30))
.center_x()
.into(),
])
.height(Length::Fill)
.width(Length::Fill),
)
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.width(Length::Units(400))
.height(Length::Units(180))
.padding(20)
.into()
} else if is_selecting_language {
self.language_selection.view(
fonts,
imgs,
i18n,
language_metadatas,
selected_language_index,
button_style,
)
} else {
self.banner
.view(fonts, imgs, login_info, i18n, button_style)
};
let central_column = Container::new(central_content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y();
let v_logo = Container::new(Image::new(imgs.v_logo).fix_aspect_ratio())
.padding(3)
.width(Length::Units(230));
let version = iced::Text::new(version).size(fonts.cyri.scale(15));
let right_column = Container::new(
Column::with_children(vec![v_logo.into(), version.into()]).align_items(Align::Center),
)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Align::End);
Row::with_children(vec![
left_column,
central_column.into(),
right_column.into(),
])
.width(Length::Fill)
.height(Length::Fill)
.spacing(10)
.into()
}
}
pub struct LanguageSelectBanner {
okay_button: button::State,
language_buttons: Vec<button::State>,
selection_list: scrollable::State,
}
impl LanguageSelectBanner {
fn new() -> Self {
Self {
okay_button: Default::default(),
language_buttons: Default::default(),
selection_list: Default::default(),
}
}
fn view(
&mut self,
fonts: &Fonts,
imgs: &Imgs,
i18n: &Localization,
language_metadatas: &[crate::i18n::LanguageMetadata],
selected_language_index: Option<usize>,
button_style: style::button::Style,
) -> Element<Message> {
// Reset button states if languages were added / removed
if self.language_buttons.len() != language_metadatas.len() {
self.language_buttons = vec![Default::default(); language_metadatas.len()];
}
let title = Text::new(i18n.get("main.login.select_language"))
.size(fonts.cyri.scale(35))
.horizontal_alignment(iced::HorizontalAlignment::Center);
let mut list = Scrollable::new(&mut self.selection_list)
.spacing(8)
.height(Length::Fill)
.align_items(Align::Start);
let list_items = self
.language_buttons
.iter_mut()
.zip(language_metadatas)
.enumerate()
.map(|(i, (state, lang))| {
let color = if Some(i) == selected_language_index {
(97, 255, 18)
} else {
(97, 97, 25)
};
let button = Button::new(
state,
Row::with_children(vec![
Space::new(Length::FillPortion(5), Length::Units(0)).into(),
Text::new(lang.language_name.clone())
.width(Length::FillPortion(95))
.size(fonts.cyri.scale(25))
.vertical_alignment(iced::VerticalAlignment::Center)
.into(),
]),
)
.style(
style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover)
.press_image(imgs.selection_press)
.image_color(vek::Rgba::new(color.0, color.1, color.2, 192)),
)
.min_height(56)
.on_press(Message::LanguageChanged(i));
Row::with_children(vec![
Space::new(Length::FillPortion(3), Length::Units(0)).into(),
button.width(Length::FillPortion(92)).into(),
Space::new(Length::FillPortion(5), Length::Units(0)).into(),
])
});
for item in list_items {
list = list.push(item);
}
let okay_button = Container::new(neat_button(
&mut self.okay_button,
i18n.get("common.okay"),
FILL_FRAC_TWO,
button_style,
Some(Message::OpenLanguageMenu),
))
.center_x()
.max_width(200);
let content = Column::with_children(vec![title.into(), list.into(), okay_button.into()])
.spacing(8)
.width(Length::Fill)
.height(Length::FillPortion(38))
.align_items(Align::Center);
let selection_menu = BackgroundContainer::new(
CompoundGraphic::from_graphics(vec![
Graphic::image(imgs.banner_top, [138, 17], [0, 0]),
Graphic::rect(Rgba::new(0, 0, 0, 230), [130, 165], [4, 17]),
// TODO: use non image gradient
Graphic::gradient(Rgba::new(0, 0, 0, 230), Rgba::zero(), [130, 50], [4, 182]),
])
.fix_aspect_ratio()
.height(Length::Fill),
content,
)
.padding(Padding::new().horizontal(5).top(15).bottom(50))
.max_width(350);
selection_menu.into()
}
}
pub struct LoginBanner {
pub username: text_input::State,
pub password: text_input::State,
pub server: text_input::State,
multiplayer_button: button::State,
#[cfg(feature = "singleplayer")]
singleplayer_button: button::State,
}
impl LoginBanner {
fn new() -> Self {
Self {
username: Default::default(),
password: Default::default(),
server: Default::default(),
multiplayer_button: Default::default(),
#[cfg(feature = "singleplayer")]
singleplayer_button: Default::default(),
}
}
fn view(
&mut self,
fonts: &Fonts,
imgs: &Imgs,
login_info: &LoginInfo,
i18n: &Localization,
button_style: style::button::Style,
) -> Element<Message> {
let input_text_size = fonts.cyri.scale(INPUT_TEXT_SIZE);
let banner_content = Column::with_children(vec![
Column::with_children(vec![
BackgroundContainer::new(
Image::new(imgs.input_bg)
.width(Length::Units(INPUT_WIDTH))
.fix_aspect_ratio(),
TextInput::new(
&mut self.username,
i18n.get("main.username"),
&login_info.username,
Message::Username,
)
.size(input_text_size)
.on_submit(Message::FocusPassword),
)
.padding(Padding::new().horizontal(7).top(5))
.into(),
BackgroundContainer::new(
Image::new(imgs.input_bg)
.width(Length::Units(INPUT_WIDTH))
.fix_aspect_ratio(),
TextInput::new(
&mut self.password,
i18n.get("main.password"),
&login_info.password,
Message::Password,
)
.size(input_text_size)
.password()
.on_submit(Message::Multiplayer),
)
.padding(Padding::new().horizontal(7).top(5))
.into(),
BackgroundContainer::new(
Image::new(imgs.input_bg)
.width(Length::Units(INPUT_WIDTH))
.fix_aspect_ratio(),
TextInput::new(
&mut self.server,
i18n.get("main.server"),
&login_info.server,
Message::Server,
)
.size(input_text_size)
.on_submit(Message::Multiplayer),
)
.padding(Padding::new().horizontal(7).top(5))
.into(),
])
.spacing(5)
.into(),
Space::new(Length::Fill, Length::Units(8)).into(),
Column::with_children(vec![
neat_button(
&mut self.multiplayer_button,
i18n.get("common.multiplayer"),
FILL_FRAC_TWO,
button_style,
Some(Message::Multiplayer),
),
#[cfg(feature = "singleplayer")]
neat_button(
&mut self.singleplayer_button,
i18n.get("common.singleplayer"),
FILL_FRAC_TWO,
button_style,
Some(Message::Singleplayer),
),
])
.max_width(170)
.height(Length::Units(200))
.spacing(8)
.into(),
])
.width(Length::Fill)
.align_items(Align::Center);
Container::new(banner_content)
.height(Length::Fill)
.center_y()
.into()
}
}

View File

@ -0,0 +1,574 @@
mod connecting;
// Note: Keeping in case we re-add the disclaimer
//mod disclaimer;
mod login;
mod servers;
use crate::{
i18n::{i18n_asset_key, LanguageMetadata, Localization},
render::Renderer,
ui::{
self,
fonts::IcedFonts as Fonts,
ice::{style, widget, Element, Font, IcedUi as Ui},
img_ids::{ImageGraphic, VoxelGraphic},
Graphic,
},
GlobalState,
};
use iced::{text_input, Column, Container, HorizontalAlignment, Length, Row, Space};
//ImageFrame, Tooltip,
use crate::settings::Settings;
use common::assets::Asset;
use image::DynamicImage;
use rand::{seq::SliceRandom, thread_rng};
use std::time::Duration;
// TODO: what is this? (showed up in rebase)
//const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9);
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
pub const FILL_FRAC_ONE: f32 = 0.77;
pub const FILL_FRAC_TWO: f32 = 0.53;
image_ids_ice! {
struct Imgs {
<VoxelGraphic>
v_logo: "voxygen.element.v_logo",
<ImageGraphic>
bg: "voxygen.background.bg_main",
banner_top: "voxygen.element.frames.banner_top",
banner_gradient_bottom: "voxygen.element.frames.banner_gradient_bottom",
button: "voxygen.element.buttons.button",
button_hover: "voxygen.element.buttons.button_hover",
button_press: "voxygen.element.buttons.button_press",
input_bg: "voxygen.element.misc_bg.textbox",
loading_art: "voxygen.element.frames.loading_screen.loading_bg",
loading_art_l: "voxygen.element.frames.loading_screen.loading_bg_l",
loading_art_r: "voxygen.element.frames.loading_screen.loading_bg_r",
selection: "voxygen.element.frames.selection",
selection_hover: "voxygen.element.frames.selection_hover",
selection_press: "voxygen.element.frames.selection_press",
// Animation
f1: "voxygen.element.animation.gears.1",
f2: "voxygen.element.animation.gears.2",
f3: "voxygen.element.animation.gears.3",
f4: "voxygen.element.animation.gears.4",
f5: "voxygen.element.animation.gears.5",
}
}
// Randomly loaded background images
const BG_IMGS: [&str; 13] = [
"voxygen.background.bg_1",
"voxygen.background.bg_2",
"voxygen.background.bg_3",
"voxygen.background.bg_4",
"voxygen.background.bg_5",
"voxygen.background.bg_6",
"voxygen.background.bg_7",
"voxygen.background.bg_8",
"voxygen.background.bg_9",
//"voxygen.background.bg_10",
"voxygen.background.bg_11",
//"voxygen.background.bg_12",
"voxygen.background.bg_13",
//"voxygen.background.bg_14",
"voxygen.background.bg_15",
"voxygen.background.bg_16",
];
pub enum Event {
LoginAttempt {
username: String,
password: String,
server_address: String,
},
CancelLoginAttempt,
ChangeLanguage(LanguageMetadata),
#[cfg(feature = "singleplayer")]
StartSingleplayer,
Quit,
// Note: Keeping in case we re-add the disclaimer
//DisclaimerAccepted,
AuthServerTrust(String, bool),
}
pub struct LoginInfo {
pub username: String,
pub password: String,
pub server: String,
}
enum ConnectionState {
InProgress,
AuthTrustPrompt { auth_server: String, msg: String },
}
enum Screen {
// Note: Keeping in case we re-add the disclaimer
/*Disclaimer {
screen: disclaimer::Screen,
},*/
Login {
screen: login::Screen,
// Error to display in a box
error: Option<String>,
},
Servers {
screen: servers::Screen,
},
Connecting {
screen: connecting::Screen,
connection_state: ConnectionState,
},
}
struct Controls {
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
i18n: std::sync::Arc<Localization>,
// Voxygen version
version: String,
// Alpha disclaimer
alpha: String,
selected_server_index: Option<usize>,
login_info: LoginInfo,
is_selecting_language: bool,
selected_language_index: Option<usize>,
time: f64,
screen: Screen,
}
#[derive(Clone)]
enum Message {
Quit,
Back,
ShowServers,
#[cfg(feature = "singleplayer")]
Singleplayer,
Multiplayer,
LanguageChanged(usize),
OpenLanguageMenu,
Username(String),
Password(String),
Server(String),
ServerChanged(usize),
FocusPassword,
CancelConnect,
TrustPromptAdd,
TrustPromptCancel,
CloseError,
/* Note: Keeping in case we re-add the disclaimer
*AcceptDisclaimer, */
}
impl Controls {
fn new(
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
i18n: std::sync::Arc<Localization>,
settings: &Settings,
) -> Self {
let version = common::util::DISPLAY_VERSION_LONG.clone();
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
// Note: Keeping in case we re-add the disclaimer
let screen = /* if settings.show_disclaimer {
Screen::Disclaimer {
screen: disclaimer::Screen::new(),
}
} else { */
Screen::Login {
screen: login::Screen::new(),
error: None,
};
//};
let login_info = LoginInfo {
username: settings.networking.username.clone(),
password: String::new(),
server: settings.networking.default_server.clone(),
};
let selected_server_index = settings
.networking
.servers
.iter()
.position(|f| f == &login_info.server);
let language_metadatas = crate::i18n::list_localizations();
let selected_language_index = language_metadatas
.iter()
.position(|f| f.language_identifier == settings.language.selected_language);
Self {
fonts,
imgs,
bg_img,
i18n,
version,
alpha,
selected_server_index,
login_info,
is_selecting_language: false,
selected_language_index,
time: 0.0,
screen,
}
}
fn view(&mut self, settings: &Settings, dt: f32) -> Element<Message> {
self.time += dt as f64;
// TODO: consider setting this as the default in the renderer
let button_style = style::button::Style::new(self.imgs.button)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.text_color(TEXT_COLOR)
.disabled_text_color(DISABLED_TEXT_COLOR);
let alpha = iced::Text::new(&self.alpha)
.size(self.fonts.cyri.scale(12))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center);
let top_text = Row::with_children(vec![
Space::new(Length::Fill, Length::Shrink).into(),
alpha.into(),
if matches!(&self.screen, Screen::Login { .. }) {
// Login screen shows the Velroen logo over the version
Space::new(Length::Fill, Length::Shrink).into()
} else {
iced::Text::new(&self.version)
.size(self.fonts.cyri.scale(15))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Right)
.into()
},
])
.padding(3)
.width(Length::Fill);
let bg_img = if matches!(&self.screen, Screen::Connecting {..}) {
self.bg_img
} else {
self.imgs.bg
};
let language_metadatas = crate::i18n::list_localizations();
// TODO: make any large text blocks scrollable so that if the area is to
// small they can still be read
let content = match &mut self.screen {
// Note: Keeping in case we re-add the disclaimer
//Screen::Disclaimer { screen } => screen.view(&self.fonts, &self.i18n, button_style),
Screen::Login { screen, error } => screen.view(
&self.fonts,
&self.imgs,
&self.login_info,
error.as_deref(),
&self.i18n,
self.is_selecting_language,
self.selected_language_index,
&language_metadatas,
button_style,
&self.version,
),
Screen::Servers { screen } => screen.view(
&self.fonts,
&self.imgs,
&settings.networking.servers,
self.selected_server_index,
&self.i18n,
button_style,
),
Screen::Connecting {
screen,
connection_state,
} => screen.view(
&self.fonts,
&self.imgs,
&connection_state,
self.time,
&self.i18n,
button_style,
settings.gameplay.loading_tips,
),
};
Container::new(
Column::with_children(vec![top_text.into(), content])
.spacing(3)
.width(Length::Fill)
.height(Length::Fill),
)
.style(style::container::Style::image(bg_img))
.into()
}
fn update(&mut self, message: Message, events: &mut Vec<Event>, settings: &Settings) {
let servers = &settings.networking.servers;
let mut language_metadatas = crate::i18n::list_localizations();
match message {
Message::Quit => events.push(Event::Quit),
Message::Back => {
self.screen = Screen::Login {
screen: login::Screen::new(),
error: None,
};
},
Message::ShowServers => {
if matches!(&self.screen, Screen::Login {..}) {
self.selected_server_index =
servers.iter().position(|f| f == &self.login_info.server);
self.screen = Screen::Servers {
screen: servers::Screen::new(),
};
}
},
#[cfg(feature = "singleplayer")]
Message::Singleplayer => {
self.screen = Screen::Connecting {
screen: connecting::Screen::new(),
connection_state: ConnectionState::InProgress,
};
events.push(Event::StartSingleplayer);
},
Message::Multiplayer => {
self.screen = Screen::Connecting {
screen: connecting::Screen::new(),
connection_state: ConnectionState::InProgress,
};
events.push(Event::LoginAttempt {
username: self.login_info.username.clone(),
password: self.login_info.password.clone(),
server_address: self.login_info.server.clone(),
});
},
Message::Username(new_value) => self.login_info.username = new_value,
Message::LanguageChanged(new_value) => {
self.selected_language_index = Some(new_value);
events.push(Event::ChangeLanguage(language_metadatas.remove(new_value)));
},
Message::OpenLanguageMenu => self.is_selecting_language = !self.is_selecting_language,
Message::Password(new_value) => self.login_info.password = new_value,
Message::Server(new_value) => {
self.login_info.server = new_value;
},
Message::ServerChanged(new_value) => {
self.selected_server_index = Some(new_value);
self.login_info.server = servers[new_value].clone();
},
Message::FocusPassword => {
if let Screen::Login { screen, .. } = &mut self.screen {
screen.banner.password = text_input::State::focused();
screen.banner.username = text_input::State::new();
}
},
Message::CancelConnect => {
self.exit_connect_screen();
events.push(Event::CancelLoginAttempt);
},
msg @ Message::TrustPromptAdd | msg @ Message::TrustPromptCancel => {
if let Screen::Connecting {
connection_state, ..
} = &mut self.screen
{
if let ConnectionState::AuthTrustPrompt { auth_server, .. } = connection_state {
let auth_server = std::mem::take(auth_server);
let added = matches!(msg, Message::TrustPromptAdd);
*connection_state = ConnectionState::InProgress;
events.push(Event::AuthServerTrust(auth_server, added));
}
}
},
Message::CloseError => {
if let Screen::Login { error, .. } = &mut self.screen {
*error = None;
}
},
/* Note: Keeping in case we re-add the disclaimer */
/*Message::AcceptDisclaimer => {
if let Screen::Disclaimer { .. } = &self.screen {
events.push(Event::DisclaimerAccepted);
self.screen = Screen::Login {
screen: login::Screen::new(),
error: None,
};
}
},*/
}
}
// Connection successful of failed
fn exit_connect_screen(&mut self) {
if matches!(&self.screen, Screen::Connecting {..}) {
self.screen = Screen::Login {
screen: login::Screen::new(),
error: None,
}
}
}
fn auth_trust_prompt(&mut self, auth_server: String) {
if let Screen::Connecting {
connection_state, ..
} = &mut self.screen
{
let msg = format!(
"Warning: The server you are trying to connect to has provided this \
authentication server address:\n\n{}\n\nbut it is not in your list of trusted \
authentication servers.\n\nMake sure that you trust this site and owner to not \
try and bruteforce your password!",
&auth_server
);
*connection_state = ConnectionState::AuthTrustPrompt { auth_server, msg };
}
}
fn connection_error(&mut self, error: String) {
if matches!(&self.screen, Screen::Connecting {..}) {
self.screen = Screen::Login {
screen: login::Screen::new(),
error: Some(error),
}
}
}
fn tab(&mut self) {
if let Screen::Login { screen, .. } = &mut self.screen {
// TODO: add select all function in iced
if screen.banner.username.is_focused() {
screen.banner.username = iced::text_input::State::new();
screen.banner.password = iced::text_input::State::focused();
screen.banner.password.move_cursor_to_end();
} else if screen.banner.password.is_focused() {
screen.banner.password = iced::text_input::State::new();
screen.banner.server = iced::text_input::State::focused();
screen.banner.server.move_cursor_to_end();
} else if screen.banner.server.is_focused() {
screen.banner.server = iced::text_input::State::new();
screen.banner.username = iced::text_input::State::focused();
screen.banner.username.move_cursor_to_end();
}
}
}
}
pub struct MainMenuUi {
ui: Ui,
// TODO: re add this
// tip_no: u16,
controls: Controls,
}
impl<'a> MainMenuUi {
pub fn new(global_state: &mut GlobalState) -> Self {
// Load language
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// TODO: don't add default font twice
let font = {
use std::io::Read;
let mut buf = Vec::new();
common::assets::load_file("voxygen.font.haxrcorp_4089_cyrillic_altgr_extended", &[
"ttf",
])
.unwrap()
.read_to_end(&mut buf)
.unwrap();
Font::try_from_vec(buf).unwrap()
};
let mut ui = Ui::new(
&mut global_state.window,
font,
global_state.settings.gameplay.ui_scale,
)
.unwrap();
let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts");
let bg_img_spec = BG_IMGS.choose(&mut thread_rng()).unwrap();
let controls = Controls::new(
fonts,
Imgs::load(&mut ui).expect("Failed to load images"),
ui.add_graphic(Graphic::Image(DynamicImage::load_expect(bg_img_spec), None)),
i18n,
&global_state.settings,
);
Self { ui, controls }
}
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>) {
self.controls.i18n = i18n;
self.controls.fonts = Fonts::load(&self.controls.i18n.fonts, &mut self.ui)
.expect("Impossible to load fonts!");
}
pub fn auth_trust_prompt(&mut self, auth_server: String) {
self.controls.auth_trust_prompt(auth_server);
}
pub fn show_info(&mut self, msg: String) { self.controls.connection_error(msg); }
pub fn connected(&mut self) { self.controls.exit_connect_screen(); }
pub fn cancel_connection(&mut self) { self.controls.exit_connect_screen(); }
pub fn handle_event(&mut self, event: ui::ice::Event) {
// Tab for input fields
use iced::keyboard;
if matches!(
&event,
iced::Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Tab,
..
})
) {
self.controls.tab();
}
self.ui.handle_event(event);
}
pub fn maintain(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
let mut events = Vec::new();
let (messages, _) = self.ui.maintain(
self.controls.view(&global_state.settings, dt.as_secs_f32()),
global_state.window.renderer_mut(),
);
messages.into_iter().for_each(|message| {
self.controls
.update(message, &mut events, &global_state.settings)
});
events
}
pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer); }
}

View File

@ -0,0 +1,129 @@
use super::{Imgs, Message, FILL_FRAC_ONE};
use crate::{
i18n::Localization,
ui::{
fonts::IcedFonts as Fonts,
ice::{component::neat_button, style, Element},
},
};
use iced::{
button, scrollable, Align, Button, Column, Container, Length, Row, Scrollable, Space, Text,
};
pub struct Screen {
back_button: button::State,
server_buttons: Vec<button::State>,
servers_list: scrollable::State,
}
impl Screen {
pub fn new() -> Self {
Self {
back_button: Default::default(),
server_buttons: vec![],
servers_list: Default::default(),
}
}
pub(super) fn view(
&mut self,
fonts: &Fonts,
imgs: &Imgs,
servers: &[impl AsRef<str>],
selected_server_index: Option<usize>,
i18n: &Localization,
button_style: style::button::Style,
) -> Element<Message> {
let title = Text::new(i18n.get("main.servers.select_server"))
.size(fonts.cyri.scale(35))
.width(Length::Fill)
.horizontal_alignment(iced::HorizontalAlignment::Center);
let back_button = Container::new(
Container::new(neat_button(
&mut self.back_button,
i18n.get("common.back"),
FILL_FRAC_ONE,
button_style,
Some(Message::Back),
))
.max_width(200),
)
.width(Length::Fill)
.align_x(Align::Center);
let mut list = Scrollable::new(&mut self.servers_list)
.spacing(8)
.align_items(Align::Start)
.width(Length::Fill)
.height(Length::Fill);
// Reset button states if servers were added / removed
if self.server_buttons.len() != servers.len() {
self.server_buttons = vec![Default::default(); servers.len()];
}
let list_items =
self.server_buttons
.iter_mut()
.zip(servers)
.enumerate()
.map(|(i, (state, server))| {
let color = if Some(i) == selected_server_index {
(97, 255, 18)
} else {
(97, 97, 25)
};
let button = Button::new(
state,
Row::with_children(vec![
Space::new(Length::FillPortion(5), Length::Units(0)).into(),
Text::new(server.as_ref())
.size(fonts.cyri.scale(30))
.width(Length::FillPortion(95))
.vertical_alignment(iced::VerticalAlignment::Center)
.into(),
]),
)
.style(
style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover)
.press_image(imgs.selection_press)
.image_color(vek::Rgba::new(color.0, color.1, color.2, 255)),
)
.min_height(100)
.on_press(Message::ServerChanged(i));
Row::with_children(vec![
Space::new(Length::FillPortion(3), Length::Units(0)).into(),
button.width(Length::FillPortion(92)).into(),
Space::new(Length::FillPortion(5), Length::Units(0)).into(),
])
});
for item in list_items {
list = list.push(item);
}
Container::new(
Container::new(
Column::with_children(vec![title.into(), list.into(), back_button.into()])
.width(Length::Fill)
.height(Length::Fill)
.spacing(10)
.padding(20),
)
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.max_width(500),
)
.width(Length::Fill)
.align_x(Align::Center)
.padding(80)
.into()
}
}

View File

@ -57,6 +57,21 @@ impl<P: Pipeline> Mesh<P> {
self.verts.push(quad.a);
}
/// Overwrite a quad
pub fn replace_quad(&mut self, index: usize, quad: Quad<P>) {
debug_assert!(index % 3 == 0);
assert!(index + 5 < self.verts.len());
// Tri 1
self.verts[index] = quad.a.clone();
self.verts[index + 1] = quad.b;
self.verts[index + 2] = quad.c.clone();
// Tri 2
self.verts[index + 3] = quad.c;
self.verts[index + 4] = quad.d;
self.verts[index + 5] = quad.a;
}
/// Push the vertices of another mesh onto the end of this mesh.
pub fn push_mesh(&mut self, other: &Mesh<P>) { self.verts.extend_from_slice(other.vertices()); }

View File

@ -31,8 +31,9 @@ pub use self::{
sprite::{Instance as SpriteInstance, Locals as SpriteLocals, SpritePipeline},
terrain::{Locals as TerrainLocals, TerrainPipeline},
ui::{
create_quad as create_ui_quad, create_tri as create_ui_tri, Locals as UiLocals,
Mode as UiMode, UiPipeline,
create_quad as create_ui_quad,
create_quad_vert_gradient as create_ui_quad_vert_gradient, create_tri as create_ui_tri,
Locals as UiLocals, Mode as UiMode, UiPipeline,
},
GlobalModel, Globals, Light, Shadow,
},

View File

@ -87,24 +87,37 @@ impl Mode {
}
}
#[allow(clippy::many_single_char_names)]
pub fn create_quad(
rect: Aabr<f32>,
uv_rect: Aabr<f32>,
color: Rgba<f32>,
mode: Mode,
) -> Quad<UiPipeline> {
create_quad_vert_gradient(rect, uv_rect, color, color, mode)
}
#[allow(clippy::many_single_char_names)]
pub fn create_quad_vert_gradient(
rect: Aabr<f32>,
uv_rect: Aabr<f32>,
top_color: Rgba<f32>,
bottom_color: Rgba<f32>,
mode: Mode,
) -> Quad<UiPipeline> {
let top_color = top_color.into_array();
let bottom_color = bottom_color.into_array();
let center = if let Mode::ImageSourceNorth = mode {
uv_rect.center().into_array()
} else {
rect.center().into_array()
};
let mode_val = mode.value();
let v = |pos, uv| Vertex {
let v = |pos, uv, color| Vertex {
pos,
uv,
center,
color: color.into_array(),
color,
mode: mode_val,
};
let aabr_to_lbrt = |aabr: Aabr<f32>| (aabr.min.x, aabr.min.y, aabr.max.x, aabr.max.y);
@ -114,22 +127,22 @@ pub fn create_quad(
match (uv_b > uv_t, uv_l > uv_r) {
(true, true) => Quad::new(
v([r, t], [uv_l, uv_b]),
v([l, t], [uv_l, uv_t]),
v([l, b], [uv_r, uv_t]),
v([r, b], [uv_r, uv_b]),
v([r, t], [uv_l, uv_b], top_color),
v([l, t], [uv_l, uv_t], top_color),
v([l, b], [uv_r, uv_t], bottom_color),
v([r, b], [uv_r, uv_b], bottom_color),
),
(false, false) => Quad::new(
v([r, t], [uv_l, uv_b]),
v([l, t], [uv_l, uv_t]),
v([l, b], [uv_r, uv_t]),
v([r, b], [uv_r, uv_b]),
v([r, t], [uv_l, uv_b], top_color),
v([l, t], [uv_l, uv_t], top_color),
v([l, b], [uv_r, uv_t], bottom_color),
v([r, b], [uv_r, uv_b], bottom_color),
),
_ => Quad::new(
v([r, t], [uv_r, uv_t]),
v([l, t], [uv_l, uv_t]),
v([l, b], [uv_l, uv_b]),
v([r, b], [uv_r, uv_b]),
v([r, t], [uv_r, uv_t], top_color),
v([l, t], [uv_l, uv_t], top_color),
v([l, b], [uv_l, uv_b], bottom_color),
v([r, b], [uv_r, uv_b], bottom_color),
),
}
}

View File

@ -34,6 +34,16 @@ pub fn run(mut global_state: GlobalState, event_loop: EventLoop) {
if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) {
global_state.window.send_event(Event::Ui(event));
}
// iced ui events
// TODO: no clone
if let winit::event::Event::WindowEvent { event, .. } = &event {
let window = &mut global_state.window;
if let Some(event) =
ui::ice::window_event(event, window.scale_factor(), window.modifiers())
{
window.send_event(Event::IcedUi(event));
}
}
match event {
winit::event::Event::NewEvents(_) => {

View File

@ -2,7 +2,7 @@ use crate::{
audio::sfx::{SfxEvent, SfxEventItem},
ecs::MyEntity,
hud::{DebugInfo, Event as HudEvent, Hud, HudInfo, PressBehavior},
i18n::{i18n_asset_key, VoxygenLocalization},
i18n::{i18n_asset_key, Localization},
key_state::KeyState,
menu::char_selection::CharSelectionState,
render::Renderer,
@ -46,7 +46,7 @@ pub struct SessionState {
key_state: KeyState,
inputs: comp::ControllerInputs,
selected_block: Block,
voxygen_i18n: std::sync::Arc<VoxygenLocalization>,
i18n: std::sync::Arc<Localization>,
walk_forward_dir: Vec2<f32>,
walk_right_dir: Vec2<f32>,
freefly_vel: Vec3<f32>,
@ -72,7 +72,7 @@ impl SessionState {
.camera_mut()
.set_fov_deg(global_state.settings.graphics.fov);
let hud = Hud::new(global_state, &client.borrow());
let voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
@ -86,7 +86,7 @@ impl SessionState {
inputs: comp::ControllerInputs::default(),
hud,
selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)),
voxygen_i18n,
i18n,
walk_forward_dir,
walk_right_dir,
freefly_vel: Vec3::zero(),
@ -131,14 +131,14 @@ impl SessionState {
match inv_event {
InventoryUpdateEvent::CollectFailed => {
self.hud.new_message(ChatMsg {
message: self.voxygen_i18n.get("hud.chat.loot_fail").to_string(),
message: self.i18n.get("hud.chat.loot_fail").to_string(),
chat_type: ChatType::CommandError,
});
},
InventoryUpdateEvent::Collected(item) => {
self.hud.new_message(ChatMsg {
message: self
.voxygen_i18n
.i18n
.get("hud.chat.loot_msg")
.replace("{item}", item.name()),
chat_type: ChatType::Loot,
@ -150,9 +150,9 @@ impl SessionState {
client::Event::Disconnect => return Ok(TickAction::Disconnect),
client::Event::DisconnectionNotification(time) => {
let message = match time {
0 => String::from(self.voxygen_i18n.get("hud.chat.goodbye")),
0 => String::from(self.i18n.get("hud.chat.goodbye")),
_ => self
.voxygen_i18n
.i18n
.get("hud.chat.connection_lost")
.replace("{time}", time.to_string().as_str()),
};
@ -165,7 +165,7 @@ impl SessionState {
client::Event::Kicked(reason) => {
global_state.info_message = Some(format!(
"{}: {}",
self.voxygen_i18n.get("main.login.kicked").to_string(),
self.i18n.get("main.login.kicked").to_string(),
reason
));
return Ok(TickAction::Disconnect);
@ -207,7 +207,7 @@ impl PlayState for SessionState {
span!(_guard, "tick", "<Session as PlayState>::tick");
// TODO: let mut client = self.client.borrow_mut();
// NOTE: Not strictly necessary, but useful for hotloading translation changes.
self.voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
self.i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
@ -673,7 +673,7 @@ impl PlayState for SessionState {
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
Err(err) => {
global_state.info_message =
Some(self.voxygen_i18n.get("common.connection_lost").to_owned());
Some(self.i18n.get("common.connection_lost").to_owned());
error!("[session] Failed to tick the scene: {:?}", err);
return PlayStateResult::Pop;
@ -752,7 +752,7 @@ impl PlayState for SessionState {
// Look for changes in the localization files
if global_state.localization_watcher.reloaded() {
hud_events.push(HudEvent::ChangeLanguage(Box::new(
self.voxygen_i18n.metadata.clone(),
self.i18n.metadata.clone(),
)));
}
@ -980,13 +980,13 @@ impl PlayState for SessionState {
HudEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
self.voxygen_i18n = VoxygenLocalization::load_watched(
self.i18n = Localization::load_watched(
&i18n_asset_key(&global_state.settings.language.selected_language),
&mut global_state.localization_watcher,
)
.unwrap();
self.voxygen_i18n.log_missing_entries();
self.hud.update_language(Arc::clone(&self.voxygen_i18n));
self.i18n.log_missing_entries();
self.hud.update_language(Arc::clone(&self.i18n));
},
HudEvent::ChangeFullscreenMode(new_fullscreen_settings) => {
global_state

View File

@ -558,16 +558,16 @@ impl Default for GameplaySettings {
pub struct NetworkingSettings {
pub username: String,
pub servers: Vec<String>,
pub default_server: usize,
pub default_server: String,
pub trusted_auth_servers: HashSet<String>,
}
impl Default for NetworkingSettings {
fn default() -> Self {
Self {
username: "Username".to_string(),
username: "".to_string(),
servers: vec!["server.veloren.net".to_string()],
default_server: 0,
default_server: "server.veloren.net".to_string(),
trusted_auth_servers: ["https://auth.veloren.net"]
.iter()
.map(|s| s.to_string())

View File

@ -6,7 +6,7 @@ pub struct Event(pub Input);
impl Event {
pub fn try_from(
event: &winit::event::Event<()>,
window: &glutin::ContextWrapper<glutin::PossiblyCurrent, winit::window::Window>,
window: &winit::window::Window,
) -> Option<Self> {
use conrod_winit::*;
// A wrapper around the winit window that allows us to implement the trait
@ -26,7 +26,7 @@ impl Event {
fn hidpi_factor(&self) -> f64 { winit::window::Window::scale_factor(&self.0) }
}
convert_event!(event, &WindowRef(window.window())).map(Self)
convert_event!(event, &WindowRef(window)).map(Self)
}
pub fn is_keyboard_or_mouse(&self) -> bool {

View File

@ -1,18 +1,18 @@
use crate::i18n::{Font, VoxygenFonts};
use crate::i18n;
use common::assets::Asset;
pub struct ConrodVoxygenFont {
metadata: Font,
pub struct Font {
metadata: i18n::Font,
pub conrod_id: conrod_core::text::font::Id,
}
impl ConrodVoxygenFont {
impl Font {
#[allow(clippy::needless_return)] // TODO: Pending review in #587
pub fn new(font: &Font, ui: &mut crate::ui::Ui) -> ConrodVoxygenFont {
return Self {
pub fn new(font: &i18n::Font, ui: &mut crate::ui::Ui) -> Self {
Self {
metadata: font.clone(),
conrod_id: ui.new_font(crate::ui::Font::load_expect(&font.asset_key)),
};
conrod_id: ui.new_font(crate::ui::ice::RawFont::load_expect(&font.asset_key)),
}
}
/// Scale input size to final UI size
@ -22,14 +22,14 @@ impl ConrodVoxygenFont {
macro_rules! conrod_fonts {
($([ $( $name:ident$(,)? )* ])*) => {
$(
pub struct ConrodVoxygenFonts {
$(pub $name: ConrodVoxygenFont,)*
pub struct Fonts {
$(pub $name: Font,)*
}
impl ConrodVoxygenFonts {
pub fn load(voxygen_fonts: &VoxygenFonts, ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
impl Fonts {
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
Ok(Self {
$( $name: ConrodVoxygenFont::new(voxygen_fonts.get(stringify!($name)).unwrap(), ui),)*
$( $name: Font::new(fonts.get(stringify!($name)).unwrap(), ui),)*
})
}
}
@ -40,3 +40,43 @@ macro_rules! conrod_fonts {
conrod_fonts! {
[opensans, metamorph, alkhemi, cyri, wizard]
}
pub struct IcedFont {
metadata: i18n::Font,
pub id: crate::ui::ice::FontId,
}
impl IcedFont {
pub fn new(font: &i18n::Font, ui: &mut crate::ui::ice::IcedUi) -> Self {
Self {
metadata: font.clone(),
id: ui.add_font((*crate::ui::ice::RawFont::load_expect(&font.asset_key)).clone()),
}
}
/// Scale input size to final UI size
/// TODO: change metadata to use u16
pub fn scale(&self, value: u16) -> u16 { self.metadata.scale(value as u32) as u16 }
}
macro_rules! iced_fonts {
($([ $( $name:ident$(,)? )* ])*) => {
$(
pub struct IcedFonts {
$(pub $name: IcedFont,)*
}
impl IcedFonts {
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, common::assets::Error> {
Ok(Self {
$( $name: IcedFont::new(fonts.get(stringify!($name)).unwrap(), ui),)*
})
}
}
)*
};
}
iced_fonts! {
[opensans, metamorph, alkhemi, cyri, wizard]
}

View File

@ -28,7 +28,7 @@ pub enum Graphic {
Blank,
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum Rotation {
None,
Cw90,
@ -58,6 +58,7 @@ pub struct Id(u32);
pub struct TexId(usize);
type Parameters = (Id, Vec2<u16>);
// TODO replace with slab/slotmap
type GraphicMap = HashMap<Id, Graphic>;
enum CachedDetails {
@ -187,6 +188,27 @@ impl GraphicCache {
self.textures.get(id.0).expect("Invalid TexId used")
}
pub fn get_graphic_dims(&self, (id, rot): (Id, Rotation)) -> Option<(u32, u32)> {
use image::GenericImageView;
self.get_graphic(id)
.and_then(|graphic| match graphic {
Graphic::Image(image, _) => Some(image.dimensions()),
Graphic::Voxel(segment, _, _) => {
use common::vol::SizedVol;
let size = segment.size();
// TODO: HACK because they can be rotated arbitrarily, remove
Some((size.x, size.z))
},
Graphic::Blank => None,
})
.and_then(|(w, h)| match rot {
Rotation::None | Rotation::Cw180 => Some((w, h)),
Rotation::Cw90 | Rotation::Cw270 => Some((h, w)),
// TODO: need dims for these?
Rotation::SourceNorth | Rotation::TargetNorth => None,
})
}
pub fn clear_cache(&mut self, renderer: &mut Renderer) {
self.cache_map.clear();

View File

@ -1,4 +1,7 @@
use common::util::{linear_to_srgba, srgba_to_linear};
use common::{
span,
util::{linear_to_srgba, srgba_to_linear},
};
/// Pixel art scaling
/// Note: The current ui is locked to the pixel grid with little animation, if
/// we want smoothly moving pixel art this should be done in the shaders
@ -35,6 +38,7 @@ const EPSILON: f32 = 0.0001;
// E9: c3 = (A1 * c1 * a1 + A2 * c2 * a2) / a3
#[allow(clippy::manual_saturating_arithmetic)] // TODO: Pending review in #587
pub fn resize_pixel_art(image: &RgbaImage, new_width: u32, new_height: u32) -> RgbaImage {
span!(_guard, "resize_pixel_art");
let (width, height) = image.dimensions();
let mut new_image = RgbaImage::new(new_width, new_height);

121
voxygen/src/ui/ice/cache.rs Normal file
View File

@ -0,0 +1,121 @@
use super::graphic::{Graphic, GraphicCache, Id as GraphicId};
use crate::{
render::{Renderer, Texture},
Error,
};
use glyph_brush::GlyphBrushBuilder;
use std::cell::{RefCell, RefMut};
use vek::*;
// Multiplied by current window size
const GLYPH_CACHE_SIZE: u16 = 1;
// Glyph cache tolerances
// TODO: consider scaling based on dpi as well as providing as an option to the
// user
const SCALE_TOLERANCE: f32 = 0.5;
const POSITION_TOLERANCE: f32 = 0.5;
type GlyphBrush = glyph_brush::GlyphBrush<(Aabr<f32>, Aabr<f32>), ()>;
// TODO: might not need pub
pub type Font = glyph_brush::ab_glyph::FontArc;
#[derive(Clone, Copy, Default)]
pub struct FontId(pub(super) glyph_brush::FontId);
pub struct Cache {
glyph_brush: RefCell<GlyphBrush>,
glyph_cache_tex: Texture,
graphic_cache: GraphicCache,
}
// TODO: Should functions be returning UiError instead of Error?
impl Cache {
pub fn new(renderer: &mut Renderer, default_font: Font) -> Result<Self, Error> {
let (w, h) = renderer.get_resolution().into_tuple();
let max_texture_size = renderer.max_texture_size();
let glyph_cache_dims =
Vec2::new(w, h).map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512));
let glyph_brush = GlyphBrushBuilder::using_font(default_font)
.initial_cache_size((glyph_cache_dims.x as u32, glyph_cache_dims.y as u32))
.draw_cache_scale_tolerance(SCALE_TOLERANCE)
.draw_cache_position_tolerance(POSITION_TOLERANCE)
.build();
Ok(Self {
glyph_brush: RefCell::new(glyph_brush),
glyph_cache_tex: renderer.create_dynamic_texture(glyph_cache_dims.map(|e| e as u16))?,
graphic_cache: GraphicCache::new(renderer),
})
}
pub fn glyph_cache_tex(&self) -> &Texture { &self.glyph_cache_tex }
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphBrush, &Texture) {
(self.glyph_brush.get_mut(), &self.glyph_cache_tex)
}
pub fn glyph_cache_mut(&mut self) -> &mut GlyphBrush { self.glyph_brush.get_mut() }
pub fn glyph_calculator(&self) -> RefMut<GlyphBrush> { self.glyph_brush.borrow_mut() }
// TODO: consider not re-adding default font
pub fn add_font(&mut self, font: RawFont) -> FontId {
let font = Font::try_from_vec(font.0).unwrap();
let id = self.glyph_brush.get_mut().add_font(font);
FontId(id)
}
pub fn graphic_cache(&self) -> &GraphicCache { &self.graphic_cache }
pub fn graphic_cache_mut(&mut self) -> &mut GraphicCache { &mut self.graphic_cache }
pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId {
self.graphic_cache.add_graphic(graphic)
}
pub fn replace_graphic(&mut self, id: GraphicId, graphic: Graphic) {
self.graphic_cache.replace_graphic(id, graphic)
}
// Resizes and clears the GraphicCache
pub fn resize_graphic_cache(&mut self, renderer: &mut Renderer) {
self.graphic_cache.clear_cache(renderer);
}
// Resizes and clears the GlyphCache
pub fn resize_glyph_cache(&mut self, renderer: &mut Renderer) -> Result<(), Error> {
let max_texture_size = renderer.max_texture_size();
let cache_dims = renderer
.get_resolution()
.map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512));
let glyph_brush = self.glyph_brush.get_mut();
*glyph_brush = glyph_brush
.to_builder()
.initial_cache_size((cache_dims.x as u32, cache_dims.y as u32))
.build();
self.glyph_cache_tex = renderer.create_dynamic_texture(cache_dims.map(|e| e as u16))?;
Ok(())
}
}
// TODO: use font type instead of raw vec once we convert to full iced
#[derive(Clone)]
pub struct RawFont(pub Vec<u8>);
impl common::assets::Asset for RawFont {
const ENDINGS: &'static [&'static str] = &["ttf"];
fn parse(
mut buf_reader: std::io::BufReader<std::fs::File>,
_specifier: &str,
) -> Result<Self, common::assets::Error> {
use std::io::Read;
let mut buf = Vec::new();
buf_reader.read_to_end(&mut buf)?;
Ok(Self(buf))
}
}

View File

@ -0,0 +1,6 @@
/// Various composable helpers for making iced ui's
pub mod neat_button;
pub mod tooltip;
pub use neat_button::neat_button;
//pub use tooltip::WithTooltip;

View File

@ -0,0 +1,32 @@
use crate::ui::ice as ui;
use iced::{button::State, Button, Element, Length};
use ui::{
style::button::Style,
widget::{AspectRatioContainer, FillText},
};
pub fn neat_button<M: Clone + 'static>(
state: &mut State,
label: impl Into<String>,
fill_fraction: f32,
button_style: Style,
message: Option<M>,
) -> Element<M, ui::IcedRenderer> {
let button = Button::new(state, FillText::new(label).fill_fraction(fill_fraction))
.height(Length::Fill)
.width(Length::Fill)
.style(button_style);
let button = match message {
Some(message) => button.on_press(message),
None => button,
};
let container = AspectRatioContainer::new(button);
let container = match button_style.active().0 {
Some((img, _)) => container.ratio_of_image(img),
None => container,
};
container.into()
}

View File

@ -0,0 +1,44 @@
use crate::ui::ice as ui;
use iced::{Container, Element, Text};
use ui::{
style,
widget::{Tooltip, TooltipManager},
};
// :( all tooltips have to copy because this is needed outside the function
#[derive(Copy, Clone)]
pub struct Style {
pub container: style::container::Style,
pub text_color: iced::Color,
pub text_size: u16,
pub padding: u16,
}
/// Tooltip that is just text
pub fn text<'a, M: 'a>(text: &'a str, style: Style) -> Element<'a, M, ui::IcedRenderer> {
Container::new(
Text::new(text)
.color(style.text_color)
.size(style.text_size),
)
.style(style.container)
.padding(style.padding)
.into()
}
pub trait WithTooltip<'a, M, R: ui::widget::tooltip::Renderer> {
fn with_tooltip<H>(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R>
where
H: 'a + FnMut() -> Element<'a, M, R>;
}
impl<'a, M, R: ui::widget::tooltip::Renderer, E: Into<Element<'a, M, R>>> WithTooltip<'a, M, R>
for E
{
fn with_tooltip<H>(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R>
where
H: 'a + FnMut() -> Element<'a, M, R>,
{
Tooltip::new(self, hover_content, manager)
}
}

209
voxygen/src/ui/ice/mod.rs Normal file
View File

@ -0,0 +1,209 @@
// tooltip_manager: TooltipManager,
mod cache;
pub mod component;
mod renderer;
pub mod widget;
pub use cache::{Font, FontId, RawFont};
pub use graphic::{Id, Rotation};
pub use iced::Event;
pub use iced_winit::conversion::window_event;
pub use renderer::{style, IcedRenderer};
use super::{
graphic::{self, Graphic},
scale::{Scale, ScaleMode},
};
use crate::{render::Renderer, window::Window, Error};
use common::span;
use iced::{mouse, Cache, Size, UserInterface};
use iced_winit::Clipboard;
use vek::*;
pub type Element<'a, M> = iced::Element<'a, M, IcedRenderer>;
pub struct IcedUi {
renderer: IcedRenderer,
cache: Option<Cache>,
events: Vec<Event>,
clipboard: Clipboard,
cursor_position: Vec2<f32>,
// Scaling of the ui
scale: Scale,
window_resized: Option<Vec2<u32>>,
scale_mode_changed: bool,
}
impl IcedUi {
pub fn new(
window: &mut Window,
default_font: Font,
scale_mode: ScaleMode,
) -> Result<Self, Error> {
let scale = Scale::new(window, scale_mode, 1.2);
let renderer = window.renderer_mut();
let scaled_dims = scale.scaled_window_size().map(|e| e as f32);
// TODO: examine how much mem fonts take up and reduce clones if significant
Ok(Self {
renderer: IcedRenderer::new(renderer, scaled_dims, default_font)?,
cache: Some(Cache::new()),
events: Vec::new(),
// TODO: handle None
clipboard: Clipboard::new(window.window()).unwrap(),
cursor_position: Vec2::zero(),
scale,
window_resized: None,
scale_mode_changed: false,
})
}
/// Add a new font that is referncable via the returned Id
pub fn add_font(&mut self, font: RawFont) -> FontId { self.renderer.add_font(font) }
/// Add a new graphic that is referencable via the returned Id
pub fn add_graphic(&mut self, graphic: Graphic) -> graphic::Id {
self.renderer.add_graphic(graphic)
}
pub fn replace_graphic(&mut self, id: graphic::Id, graphic: Graphic) {
self.renderer.replace_graphic(id, graphic);
}
pub fn scale(&self) -> Scale { self.scale }
pub fn set_scaling_mode(&mut self, mode: ScaleMode) {
self.scale.set_scaling_mode(mode);
// Signal that change needs to be handled
self.scale_mode_changed = true;
}
pub fn handle_event(&mut self, event: Event) {
use iced::window;
match event {
// Intercept resizing events
// TODO: examine if we are handling dpi properly here
// ideally these values should be the logical ones
Event::Window(window::Event::Resized { width, height }) => {
if width != 0 && height != 0 {
self.window_resized = Some(Vec2::new(width, height));
}
},
// Scale cursor movement events
// Note: in some cases the scaling could be off if a resized event occured in the same
// frame, in practice this shouldn't be an issue
Event::Mouse(mouse::Event::CursorMoved { x, y }) => {
// TODO: return f32 here
let scale = self.scale.scale_factor_logical() as f32;
// TODO: determine why iced moved cursor position out of the `Cache` and if we
// may need to handle this in a different way to address
// whatever issue iced was trying to address
self.cursor_position = Vec2 {
x: x / scale,
y: y / scale,
};
self.events.push(Event::Mouse(mouse::Event::CursorMoved {
x: x / scale,
y: y / scale,
}));
},
// Scale pixel scrolling events
Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Pixels { x, y },
}) => {
// TODO: return f32 here
let scale = self.scale.scale_factor_logical() as f32;
self.events.push(Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Pixels {
x: x / scale,
y: y / scale,
},
}));
},
event => self.events.push(event),
}
}
// TODO: produce root internally???
// TODO: closure/trait for sending messages back? (take a look at higher level
// iced libs)
pub fn maintain<'a, M, E: Into<Element<'a, M>>>(
&mut self,
root: E,
renderer: &mut Renderer,
) -> (Vec<M>, mouse::Interaction) {
span!(_guard, "maintain", "IcedUi::maintain");
// Handle window resizing and scale mode changing
let scaled_dims = if let Some(new_dims) = self.window_resized.take() {
let old_scaled_dims = self.scale.scaled_window_size();
// TODO maybe use u32 in Scale to be consistent with iced
self.scale
.window_resized(new_dims.map(|e| e as f64), renderer);
let scaled_dims = self.scale.scaled_window_size();
// Avoid resetting cache if window size didn't change
(scaled_dims != old_scaled_dims).then_some(scaled_dims)
} else if self.scale_mode_changed {
Some(self.scale.scaled_window_size())
} else {
None
};
if let Some(scaled_dims) = scaled_dims {
self.scale_mode_changed = false;
self.events
.push(Event::Window(iced::window::Event::Resized {
width: scaled_dims.x as u32,
height: scaled_dims.y as u32,
}));
// Avoid panic in graphic cache when minimizing.
// Somewhat inefficient for elements that won't change size after a window
// resize
let res = renderer.get_resolution();
if res.x > 0 && res.y > 0 {
self.renderer
.resize(scaled_dims.map(|e| e as f32), renderer);
}
}
let cursor_position = iced::Point {
x: self.cursor_position.x,
y: self.cursor_position.y,
};
// TODO: convert to f32 at source
let window_size = self.scale.scaled_window_size().map(|e| e as f32);
span!(guard, "build user_interface");
let mut user_interface = UserInterface::build(
root,
Size::new(window_size.x, window_size.y),
self.cache.take().unwrap(),
&mut self.renderer,
);
drop(guard);
span!(guard, "update user_interface");
let messages = user_interface.update(
&self.events,
cursor_position,
Some(&self.clipboard),
&self.renderer,
);
drop(guard);
// Clear events
self.events.clear();
span!(guard, "draw user_interface");
let (primitive, mouse_interaction) =
user_interface.draw(&mut self.renderer, cursor_position);
drop(guard);
self.cache = Some(user_interface.into_cache());
self.renderer.draw(primitive, renderer);
(messages, mouse_interaction)
}
pub fn render(&self, renderer: &mut Renderer) { self.renderer.render(renderer, None); }
}

View File

@ -0,0 +1,12 @@
// TODO: expose to user
pub struct Defaults {
pub text_color: iced::Color,
}
impl Default for Defaults {
fn default() -> Self {
Self {
text_color: iced::Color::WHITE,
}
}
}

View File

@ -0,0 +1,858 @@
mod defaults;
mod primitive;
pub mod style;
mod widget;
pub use defaults::Defaults;
pub(self) use primitive::Primitive;
use super::{
super::graphic::{self, Graphic, TexId},
cache::Cache,
widget::image,
Font, FontId, RawFont, Rotation,
};
use crate::{
render::{
create_ui_quad, create_ui_quad_vert_gradient, Consts, DynamicModel, Globals, Mesh,
Renderer, UiLocals, UiMode, UiPipeline,
},
Error,
};
use common::{span, util::srgba_to_linear};
use std::{convert::TryInto, ops::Range};
use vek::*;
enum DrawKind {
Image(TexId),
// Text and non-textured geometry
Plain,
}
#[allow(dead_code)] // TODO: remove once WorldPos is used
enum DrawCommand {
Draw { kind: DrawKind, verts: Range<u32> },
Scissor(Aabr<u16>),
WorldPos(Option<usize>),
}
impl DrawCommand {
fn image(verts: Range<usize>, id: TexId) -> DrawCommand {
DrawCommand::Draw {
kind: DrawKind::Image(id),
// TODO: move conversion into helper method so we don't have to write it out so many
// times
verts: verts
.start
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!")
..verts
.end
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!"),
}
}
fn plain(verts: Range<usize>) -> DrawCommand {
DrawCommand::Draw {
kind: DrawKind::Plain,
verts: verts
.start
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!")
..verts
.end
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!"),
}
}
}
#[derive(PartialEq)]
enum State {
Image(TexId),
Plain,
}
// Optimization idea inspired by what I think iced wgpu renderer may be doing:
// Could have layers of things which don't intersect and thus can be reordered
// arbitrarily
pub struct IcedRenderer {
//image_map: Map<(Image, Rotation)>,
cache: Cache,
// Model for drawing the ui
model: DynamicModel<UiPipeline>,
// Consts to specify positions of ingame elements (e.g. Nametags)
ingame_locals: Vec<Consts<UiLocals>>,
// Consts for default ui drawing position (ie the interface)
interface_locals: Consts<UiLocals>,
default_globals: Consts<Globals>,
// Used to delay cache resizing until after current frame is drawn
//need_cache_resize: bool,
// Half of physical resolution
half_res: Vec2<f32>,
// Pixel perfection alignment
align: Vec2<f32>,
// Scale factor between physical and win dims
p_scale: f32,
// Pretend dims :) (i.e. scaled)
win_dims: Vec2<f32>,
// Scissor for the whole window
window_scissor: Aabr<u16>,
// Per-frame/update
current_state: State,
mesh: Mesh<UiPipeline>,
glyphs: Vec<(usize, usize, Rgba<f32>, Vec2<u32>)>,
// Output from glyph_brush in the previous frame
// It can sometimes ask you to redraw with these instead (idk if that is done with
// pre-positioned glyphs)
last_glyph_verts: Vec<(Aabr<f32>, Aabr<f32>)>,
start: usize,
// Draw commands for the next render
draw_commands: Vec<DrawCommand>,
}
impl IcedRenderer {
pub fn new(
renderer: &mut Renderer,
scaled_dims: Vec2<f32>,
default_font: Font,
) -> Result<Self, Error> {
let (half_res, align, p_scale) =
Self::calculate_resolution_dependents(renderer.get_resolution(), scaled_dims);
Ok(Self {
cache: Cache::new(renderer, default_font)?,
draw_commands: Vec::new(),
model: renderer.create_dynamic_model(100)?,
interface_locals: renderer.create_consts(&[UiLocals::default()])?,
default_globals: renderer.create_consts(&[Globals::default()])?,
ingame_locals: Vec::new(),
mesh: Mesh::new(),
glyphs: Vec::new(),
last_glyph_verts: Vec::new(),
current_state: State::Plain,
half_res,
align,
p_scale,
win_dims: scaled_dims,
window_scissor: default_scissor(renderer),
start: 0,
})
}
pub fn add_font(&mut self, font: RawFont) -> FontId { self.cache.add_font(font) }
pub fn add_graphic(&mut self, graphic: Graphic) -> graphic::Id {
self.cache.add_graphic(graphic)
}
pub fn replace_graphic(&mut self, id: graphic::Id, graphic: Graphic) {
self.cache.replace_graphic(id, graphic);
}
fn image_dims(&self, handle: image::Handle) -> (u32, u32) {
self
.cache
.graphic_cache()
.get_graphic_dims((handle, Rotation::None))
// TODO: don't unwrap
.unwrap()
}
pub fn resize(&mut self, scaled_dims: Vec2<f32>, renderer: &mut Renderer) {
self.win_dims = scaled_dims;
self.window_scissor = default_scissor(renderer);
self.update_resolution_dependents(renderer.get_resolution());
// Resize graphic cache
self.cache.resize_graphic_cache(renderer);
// Resize glyph cache
self.cache.resize_glyph_cache(renderer).unwrap();
}
pub fn draw(&mut self, primitive: Primitive, renderer: &mut Renderer) {
span!(_guard, "draw", "IcedRenderer::draw");
// Re-use memory
self.draw_commands.clear();
self.mesh.clear();
self.glyphs.clear();
self.current_state = State::Plain;
self.start = 0;
self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer);
// Enter the final command.
self.draw_commands.push(match self.current_state {
State::Plain => DrawCommand::plain(self.start..self.mesh.vertices().len()),
State::Image(id) => DrawCommand::image(self.start..self.mesh.vertices().len(), id),
});
// Draw glyph cache (use for debugging).
/*self.draw_commands
.push(DrawCommand::Scissor(default_scissor(renderer)));
self.start = self.mesh.vertices().len();
self.mesh.push_quad(create_ui_quad(
Aabr {
min: (-1.0, -1.0).into(),
max: (1.0, 1.0).into(),
},
Aabr {
min: (0.0, 1.0).into(),
max: (1.0, 0.0).into(),
},
Rgba::new(1.0, 1.0, 1.0, 0.3),
UiMode::Text,
));
self.draw_commands
.push(DrawCommand::plain(self.start..self.mesh.vertices().len()));*/
// Fill in placeholder glyph quads
let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex();
let half_res = self.half_res;
let brush_result = glyph_cache.process_queued(
|rect, tex_data| {
let offset = [rect.min[0] as u16, rect.min[1] as u16];
let size = [rect.width() as u16, rect.height() as u16];
let new_data = tex_data
.iter()
.map(|x| [255, 255, 255, *x])
.collect::<Vec<[u8; 4]>>();
if let Err(err) = renderer.update_texture(cache_tex, offset, size, &new_data) {
tracing::warn!("Failed to update glyph cache texture: {:?}", err);
}
},
// Urgh more allocation we don't need
|vertex_data| {
let uv_rect = vertex_data.tex_coords;
let uv = Aabr {
min: Vec2::new(uv_rect.min.x, uv_rect.max.y),
max: Vec2::new(uv_rect.max.x, uv_rect.min.y),
};
let pixel_coords = vertex_data.pixel_coords;
let rect = Aabr {
min: Vec2::new(
pixel_coords.min.x as f32 / half_res.x - 1.0,
1.0 - pixel_coords.max.y as f32 / half_res.y,
),
max: Vec2::new(
pixel_coords.max.x as f32 / half_res.x - 1.0,
1.0 - pixel_coords.min.y as f32 / half_res.y,
),
};
(uv, rect)
},
);
match brush_result {
Ok(brush_action) => {
match brush_action {
glyph_brush::BrushAction::Draw(verts) => self.last_glyph_verts = verts,
glyph_brush::BrushAction::ReDraw => {},
}
let glyphs = &self.glyphs;
let mesh = &mut self.mesh;
let p_scale = self.p_scale;
let half_res = self.half_res;
glyphs
.iter()
.flat_map(|(mesh_index, glyph_count, linear_color, offset)| {
let mesh_index = *mesh_index;
let linear_color = *linear_color;
// Could potentially pass this in as part of the extras
let offset =
offset.map(|e| e as f32 * p_scale) / half_res * Vec2::new(-1.0, 1.0);
(0..*glyph_count).map(move |i| (mesh_index + i * 6, linear_color, offset))
})
.zip(self.last_glyph_verts.iter())
.for_each(|((mesh_index, linear_color, offset), (uv, rect))| {
// TODO: add function to vek for this
let rect = Aabr {
min: rect.min + offset,
max: rect.max + offset,
};
mesh.replace_quad(
mesh_index,
create_ui_quad(rect, *uv, linear_color, UiMode::Text),
)
});
},
Err(glyph_brush::BrushError::TextureTooSmall { suggested: (x, y) }) => {
tracing::error!(
"Texture to small for all glyphs, would need one of the size: ({}, {})",
x,
y
);
},
}
// Create a larger dynamic model if the mesh is larger than the current model
// size.
if self.model.vbuf.len() < self.mesh.vertices().len() {
self.model = renderer
.create_dynamic_model(self.mesh.vertices().len() * 4 / 3)
.unwrap();
}
// Update model with new mesh.
renderer.update_model(&self.model, &self.mesh, 0).unwrap();
}
// Returns (half_res, align)
fn calculate_resolution_dependents(
res: Vec2<u16>,
win_dims: Vec2<f32>,
) -> (Vec2<f32>, Vec2<f32>, f32) {
let half_res = res.map(|e| e as f32 / 2.0);
let align = align(res);
// Assume to be the same in x and y for now...
let p_scale = res.x as f32 / win_dims.x;
(half_res, align, p_scale)
}
fn update_resolution_dependents(&mut self, res: Vec2<u16>) {
let (half_res, align, p_scale) = Self::calculate_resolution_dependents(res, self.win_dims);
self.half_res = half_res;
self.align = align;
self.p_scale = p_scale;
}
fn gl_aabr(&self, bounds: iced::Rectangle) -> Aabr<f32> {
let flipped_y = self.win_dims.y - bounds.y;
let half_win_dims = self.win_dims.map(|e| e / 2.0);
let half_res = self.half_res;
let min = (((Vec2::new(bounds.x, flipped_y - bounds.height) - half_win_dims)
/ half_win_dims
* half_res
+ self.align)
.map(|e| e.round())
- self.align)
/ half_res;
let max = (((Vec2::new(bounds.x + bounds.width, flipped_y) - half_win_dims)
/ half_win_dims
* half_res
+ self.align)
.map(|e| e.round())
- self.align)
/ half_res;
Aabr { min, max }
}
fn position_glyphs(
&mut self,
bounds: iced::Rectangle,
horizontal_alignment: iced::HorizontalAlignment,
vertical_alignment: iced::VerticalAlignment,
text: &str,
size: u16,
font: FontId,
) -> Vec<glyph_brush::SectionGlyph> {
use glyph_brush::{GlyphCruncher, HorizontalAlign, VerticalAlign};
// TODO: add option to align based on the geometry of the rendered glyphs
// instead of all possible glyphs
let (x, h_align) = match horizontal_alignment {
iced::HorizontalAlignment::Left => (bounds.x, HorizontalAlign::Left),
iced::HorizontalAlignment::Center => (bounds.center_x(), HorizontalAlign::Center),
iced::HorizontalAlignment::Right => (bounds.x + bounds.width, HorizontalAlign::Right),
};
let (y, v_align) = match vertical_alignment {
iced::VerticalAlignment::Top => (bounds.y, VerticalAlign::Top),
iced::VerticalAlignment::Center => (bounds.center_y(), VerticalAlign::Center),
iced::VerticalAlignment::Bottom => (bounds.y + bounds.height, VerticalAlign::Bottom),
};
let p_scale = self.p_scale;
let section = glyph_brush::Section {
screen_position: (x * p_scale, y * p_scale),
bounds: (bounds.width * p_scale, bounds.height * p_scale),
layout: glyph_brush::Layout::Wrap {
line_breaker: Default::default(),
h_align,
v_align,
},
text: vec![glyph_brush::Text {
text,
scale: (size as f32 * p_scale).into(),
font_id: font.0,
extra: (),
}],
};
self
.cache
.glyph_cache_mut()
.glyphs(section)
// We would still have to generate vertices for these even if they have no pixels
// Note: this is somewhat hacky and could fail if there is a non-whitespace character
// that is not visible (to solve this we could use the extra values in
// queue_pre_positioned to keep track of which glyphs are actually returned by
// proccess_queued)
.filter(|g| {
!text[g.byte_index..]
.chars()
.next()
.unwrap()
.is_whitespace()
})
.cloned()
.collect()
}
fn draw_primitive(
&mut self,
primitive: Primitive,
offset: Vec2<u32>,
alpha: f32,
renderer: &mut Renderer,
) {
match primitive {
Primitive::Group { primitives } => {
primitives
.into_iter()
.for_each(|p| self.draw_primitive(p, offset, alpha, renderer));
},
Primitive::Image {
handle,
bounds,
color,
source_rect,
} => {
let color = srgba_to_linear(color.map(|e| e as f32 / 255.0));
let color = apply_alpha(color, alpha);
// Don't draw a transparent image.
if color.a == 0.0 {
return;
}
let (graphic_id, rotation) = handle;
let gl_aabr = self.gl_aabr(iced::Rectangle {
x: bounds.x - offset.x as f32,
y: bounds.y - offset.y as f32,
..bounds
});
let graphic_cache = self.cache.graphic_cache_mut();
let half_res = self.half_res; // Make borrow checker happy by avoiding self in closure
let (source_aabr, gl_size) = {
// Transform the source rectangle into uv coordinate.
// TODO: Make sure this is right. Especially the conversions.
let ((uv_l, uv_r, uv_b, uv_t), gl_size) = match graphic_cache
.get_graphic(graphic_id)
{
Some(Graphic::Blank) | None => return,
Some(Graphic::Image(image, ..)) => {
source_rect.and_then(|src_rect| {
#[rustfmt::skip] use ::image::GenericImageView;
let (image_w, image_h) = image.dimensions();
let (source_w, source_h) = src_rect.size().into_tuple();
let gl_size = gl_aabr.size();
if image_w == 0
|| image_h == 0
|| source_w < 1.0
|| source_h < 1.0
|| gl_size.reduce_partial_min() < f32::EPSILON
{
None
} else {
// TODO: do this earlier
// Multiply drawn image size by ratio of original image
// size to
// source rectangle size (since as the proportion of the
// image gets
// smaller, the drawn size should get bigger), up to the
// actual
// size of the original image.
let ratio_x = (image_w as f32 / source_w)
.min((image_w as f32 / (gl_size.w * half_res.x)).max(1.0));
let ratio_y = (image_h as f32 / source_h)
.min((image_h as f32 / (gl_size.h * half_res.y)).max(1.0));
let (l, b) = src_rect.min.into_tuple();
let (r, t) = src_rect.max.into_tuple();
Some((
(
l / image_w as f32, /* * ratio_x*/
r / image_w as f32, /* * ratio_x*/
b / image_h as f32, /* * ratio_y*/
t / image_h as f32, /* * ratio_y*/
),
Extent2::new(
gl_size.w as f32 * ratio_x,
gl_size.h as f32 * ratio_y,
),
))
/* ((l / image_w as f32),
(r / image_w as f32),
(b / image_h as f32),
(t / image_h as f32)) */
}
})
},
// No easy way to interpret source_rect for voxels...
Some(Graphic::Voxel(..)) => None,
}
.unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size()));
(
Aabr {
min: Vec2::new(uv_l, uv_b),
max: Vec2::new(uv_r, uv_t),
},
gl_size,
)
};
let resolution = Vec2::new(
(gl_size.w * self.half_res.x).round() as u16,
(gl_size.h * self.half_res.y).round() as u16,
);
// Don't do anything if resolution is zero
if resolution.map(|e| e == 0).reduce_or() {
return;
// TODO: consider logging uneeded elements
}
// Cache graphic at particular resolution.
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
renderer,
graphic_id,
resolution,
// TODO: take f32 here
source_aabr.map(|e| e as f64),
rotation,
) {
// TODO: get dims from graphic_cache (or have it return floats directly)
Some((aabr, tex_id)) => {
let cache_dims = graphic_cache
.get_tex(tex_id)
.get_dimensions()
.map(|e| e as f32);
let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
(Aabr { min, max }, tex_id)
},
None => return,
};
// Switch to the image state if we are not in it already or if a different
// texture id was being used.
self.switch_state(State::Image(tex_id));
self.mesh
.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image));
},
Primitive::Gradient {
bounds,
top_linear_color,
bottom_linear_color,
} => {
// Don't draw a transparent rectangle.
let top_linear_color = apply_alpha(top_linear_color, alpha);
let bottom_linear_color = apply_alpha(bottom_linear_color, alpha);
if top_linear_color.a == 0.0 && bottom_linear_color.a == 0.0 {
return;
}
self.switch_state(State::Plain);
let gl_aabr = self.gl_aabr(iced::Rectangle {
x: bounds.x - offset.x as f32,
y: bounds.y - offset.y as f32,
..bounds
});
self.mesh.push_quad(create_ui_quad_vert_gradient(
gl_aabr,
Aabr {
min: Vec2::zero(),
max: Vec2::zero(),
},
top_linear_color,
bottom_linear_color,
UiMode::Geometry,
));
},
Primitive::Rectangle {
bounds,
linear_color,
} => {
let linear_color = apply_alpha(linear_color, alpha);
// Don't draw a transparent rectangle.
if linear_color.a == 0.0 {
return;
}
self.switch_state(State::Plain);
let gl_aabr = self.gl_aabr(iced::Rectangle {
x: bounds.x - offset.x as f32,
y: bounds.y - offset.y as f32,
..bounds
});
self.mesh.push_quad(create_ui_quad(
gl_aabr,
Aabr {
min: Vec2::zero(),
max: Vec2::zero(),
},
linear_color,
UiMode::Geometry,
));
},
Primitive::Text {
glyphs,
bounds: _, // iced::Rectangle
linear_color,
} => {
let linear_color = apply_alpha(linear_color, alpha);
self.switch_state(State::Plain);
// TODO: makes sure we are not doing all this work for hidden text
// e.g. in chat
let glyph_cache = self.cache.glyph_cache_mut();
// Count glyphs
let glyph_count = glyphs.len();
// Queue the glyphs to be cached.
glyph_cache.queue_pre_positioned(
glyphs,
// TODO: glyph_brush should document that these need to be the same length
vec![(); glyph_count],
// Since we already passed in `bounds` to position the glyphs some of this
// seems redundant...
// Note: we can't actually use this because dropping glyphs messeses up the
// counting and there is not a method provided to drop out of bounds
// glyphs while positioning them
// Note: keeping commented code in case how we handle text changes
glyph_brush::ab_glyph::Rect {
min: glyph_brush::ab_glyph::point(
-10000.0, //bounds.x * self.p_scale,
-10000.0, //bounds.y * self.p_scale,
),
max: glyph_brush::ab_glyph::point(
10000.0, //(bounds.x + bounds.width) * self.p_scale,
10000.0, //(bounds.y + bounds.height) * self.p_scale,
),
},
);
// Leave ui and verts blank to fill in when processing cached glyphs
let zero_aabr = Aabr {
min: Vec2::broadcast(0.0),
max: Vec2::broadcast(0.0),
};
self.glyphs.push((
self.mesh.vertices().len(),
glyph_count,
linear_color,
offset,
));
for _ in 0..glyph_count {
// Push placeholder quad
// Note: moving to some sort of layering / z based system would be an
// alternative to this (and might help with reducing draw
// calls)
self.mesh.push_quad(create_ui_quad(
zero_aabr,
zero_aabr,
linear_color,
UiMode::Text,
));
}
},
Primitive::Clip {
bounds,
offset: clip_offset,
content,
} => {
let new_scissor = {
// TODO: incorporate current offset for nested Clips
let intersection = Aabr {
min: Vec2 {
x: (bounds.x * self.p_scale) as u16,
y: (bounds.y * self.p_scale) as u16,
},
max: Vec2 {
x: ((bounds.x + bounds.width) * self.p_scale) as u16,
y: ((bounds.y + bounds.height) * self.p_scale) as u16,
},
}
.intersection(self.window_scissor);
if intersection.is_valid() {
intersection
} else {
Aabr::new_empty(Vec2::zero())
}
};
// Not expecting this case: new_scissor == current_scissor
// So not optimizing for it
// Finish the current command.
// TODO: ensure we never push empty commands
self.draw_commands.push(match self.current_state {
State::Plain => DrawCommand::plain(self.start..self.mesh.vertices().len()),
State::Image(id) => {
DrawCommand::image(self.start..self.mesh.vertices().len(), id)
},
});
self.start = self.mesh.vertices().len();
self.draw_commands.push(DrawCommand::Scissor(new_scissor));
// TODO: support nested clips?
// TODO: if last command is a clip changing back to the default replace it with
// this
// TODO: cull primitives outside the current scissor
// Renderer child
self.draw_primitive(*content, offset + clip_offset, alpha, renderer);
// Reset scissor
self.draw_commands.push(match self.current_state {
State::Plain => DrawCommand::plain(self.start..self.mesh.vertices().len()),
State::Image(id) => {
DrawCommand::image(self.start..self.mesh.vertices().len(), id)
},
});
self.start = self.mesh.vertices().len();
self.draw_commands
.push(DrawCommand::Scissor(self.window_scissor));
},
Primitive::Opacity { alpha: a, content } => {
self.draw_primitive(*content, offset, alpha * a, renderer);
},
Primitive::Nothing => {},
}
}
// Switches to the specified state if not already in it
// If switch occurs current state is converted into a draw command
fn switch_state(&mut self, state: State) {
if self.current_state != state {
let vert_range = self.start..self.mesh.vertices().len();
let draw_command = match self.current_state {
State::Plain => DrawCommand::plain(vert_range),
State::Image(id) => DrawCommand::image(vert_range, id),
};
self.draw_commands.push(draw_command);
self.start = self.mesh.vertices().len();
self.current_state = state;
}
}
pub fn render(&self, renderer: &mut Renderer, maybe_globals: Option<&Consts<Globals>>) {
span!(_guard, "render", "IcedRenderer::render");
let mut scissor = default_scissor(renderer);
let globals = maybe_globals.unwrap_or(&self.default_globals);
let mut locals = &self.interface_locals;
for draw_command in self.draw_commands.iter() {
match draw_command {
DrawCommand::Scissor(new_scissor) => {
scissor = *new_scissor;
},
DrawCommand::WorldPos(index) => {
locals = index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]);
},
DrawCommand::Draw { kind, verts } => {
let tex = match kind {
DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
DrawKind::Plain => self.cache.glyph_cache_tex(),
};
let model = self.model.submodel(verts.clone());
renderer.render_ui_element(model, tex, scissor, globals, locals);
},
}
}
}
}
// Given the the resolution determines the offset needed to align integer
// offsets from the center of the sceen to pixels
#[inline(always)]
fn align(res: Vec2<u16>) -> Vec2<f32> {
// TODO: does this logic still apply in iced's coordinate system?
// If the resolution is odd then the center of the screen will be within the
// middle of a pixel so we need to offset by 0.5 pixels to be on the edge of
// a pixel
res.map(|e| (e & 1) as f32 * 0.5)
}
fn default_scissor(renderer: &Renderer) -> Aabr<u16> {
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as u16).into_tuple();
Aabr {
min: Vec2 { x: 0, y: 0 },
max: Vec2 {
x: screen_w,
y: screen_h,
},
}
}
impl iced::Renderer for IcedRenderer {
// Default styling
type Defaults = Defaults;
// TODO: use graph of primitives to enable diffing???
type Output = (Primitive, iced::mouse::Interaction);
#[allow(clippy::let_and_return)]
fn layout<'a, M>(
&mut self,
element: &iced::Element<'a, M, Self>,
limits: &iced::layout::Limits,
) -> iced::layout::Node {
span!(_guard, "layout", "IcedRenderer::layout");
let node = element.layout(self, limits);
// Trim text measurements cache?
node
}
fn overlay(
&mut self,
(base_primitive, base_interaction): Self::Output,
(overlay_primitive, overlay_interaction): Self::Output,
overlay_bounds: iced::Rectangle,
) -> Self::Output {
span!(_guard, "overlay", "IcedRenderer::overlay");
(
Primitive::Group {
primitives: vec![base_primitive, Primitive::Clip {
bounds: iced::Rectangle {
// TODO: do we need this + 0.5?
width: overlay_bounds.width + 0.5,
height: overlay_bounds.height + 0.5,
..overlay_bounds
},
offset: Vec2::new(0, 0),
content: Box::new(overlay_primitive),
}],
},
base_interaction.max(overlay_interaction),
)
}
}
fn apply_alpha(color: Rgba<f32>, alpha: f32) -> Rgba<f32> {
Rgba {
a: alpha * color.a,
..color
}
}
// TODO: impl Debugger

View File

@ -0,0 +1,42 @@
use crate::ui::{graphic, ice::widget::image};
#[derive(Debug)]
pub enum Primitive {
// Allocation :(
Group {
primitives: Vec<Primitive>,
},
Image {
handle: (image::Handle, graphic::Rotation),
bounds: iced::Rectangle,
color: vek::Rgba<u8>,
source_rect: Option<vek::Aabr<f32>>,
},
// A vertical gradient
// TODO: could be combined with rectangle
Gradient {
bounds: iced::Rectangle,
top_linear_color: vek::Rgba<f32>,
bottom_linear_color: vek::Rgba<f32>,
},
Rectangle {
bounds: iced::Rectangle,
linear_color: vek::Rgba<f32>,
},
Text {
glyphs: Vec<glyph_brush::SectionGlyph>,
bounds: iced::Rectangle,
linear_color: vek::Rgba<f32>,
},
Clip {
bounds: iced::Rectangle,
offset: vek::Vec2<u32>,
content: Box<Primitive>,
},
// Make content translucent
Opacity {
alpha: f32,
content: Box<Primitive>,
},
Nothing,
}

View File

@ -0,0 +1,118 @@
use super::super::super::widget::image;
use iced::Color;
use vek::Rgba;
#[derive(Clone, Copy)]
struct Background {
default: image::Handle,
hover: image::Handle,
press: image::Handle,
color: Rgba<u8>,
}
impl Background {
fn new(image: image::Handle) -> Self {
Self {
default: image,
hover: image,
press: image,
color: Rgba::white(),
}
}
}
// TODO: consider a different place for this
// Note: for now all buttons have an image background
#[derive(Clone, Copy)]
pub struct Style {
background: Option<Background>,
enabled_text: Color,
disabled_text: Color,
}
impl Style {
pub fn new(image: image::Handle) -> Self {
Self {
background: Some(Background::new(image)),
..Default::default()
}
}
pub fn hover_image(mut self, image: image::Handle) -> Self {
self.background = Some(match self.background {
Some(mut background) => {
background.hover = image;
background
},
None => Background::new(image),
});
self
}
pub fn press_image(mut self, image: image::Handle) -> Self {
self.background = Some(match self.background {
Some(mut background) => {
background.press = image;
background
},
None => Background::new(image),
});
self
}
// TODO: this needs to be refactored since the color isn't used if there is no
// background
pub fn image_color(mut self, color: Rgba<u8>) -> Self {
if let Some(background) = &mut self.background {
background.color = color;
}
self
}
pub fn text_color(mut self, color: Color) -> Self {
self.enabled_text = color;
self
}
pub fn disabled_text_color(mut self, color: Color) -> Self {
self.disabled_text = color;
self
}
pub fn disabled(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| (b.default, b.color)),
self.disabled_text,
)
}
pub fn pressed(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| (b.press, b.color)),
self.enabled_text,
)
}
pub fn hovered(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| (b.hover, b.color)),
self.enabled_text,
)
}
pub fn active(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| (b.default, b.color)),
self.enabled_text,
)
}
}
impl Default for Style {
fn default() -> Self {
Self {
background: None,
enabled_text: Color::WHITE,
disabled_text: Color::from_rgb(0.5, 0.5, 0.5),
}
}
}

View File

@ -0,0 +1,55 @@
use super::super::super::widget::image;
use vek::Rgba;
/// Container Border
#[derive(Clone, Copy)]
pub enum Border {
DoubleCornerless {
inner: Rgba<u8>,
outer: Rgba<u8>,
},
Image {
corner: image::Handle,
edge: image::Handle,
},
None,
}
/// Background of the container
#[derive(Clone, Copy)]
pub enum Style {
Image(image::Handle, Rgba<u8>),
Color(Rgba<u8>, Border),
None,
}
impl Style {
/// Shorthand for common case where the color of the image is not modified
pub fn image(image: image::Handle) -> Self { Self::Image(image, Rgba::broadcast(255)) }
/// Shorthand for a color background with no border
pub fn color(color: Rgba<u8>) -> Self { Self::Color(color, Border::None) }
/// Shorthand for a color background with a cornerless border
pub fn color_with_double_cornerless_border(
color: Rgba<u8>,
inner: Rgba<u8>,
outer: Rgba<u8>,
) -> Self {
Self::Color(color, Border::DoubleCornerless { inner, outer })
}
/// Shorthand for a color background with image borders where the corners
/// are inset
pub fn color_with_image_border(
color: Rgba<u8>,
corner: image::Handle,
edge: image::Handle,
) -> Self {
Self::Color(color, Border::Image { corner, edge })
}
}
impl Default for Style {
fn default() -> Self { Self::None }
}

View File

@ -0,0 +1,4 @@
pub mod button;
pub mod container;
pub mod scrollable;
pub mod slider;

View File

@ -0,0 +1,33 @@
use super::super::super::widget::image;
use vek::Rgba;
#[derive(Clone, Copy)]
pub struct Style {
pub track: Option<Track>,
pub scroller: Scroller,
}
impl Default for Style {
fn default() -> Self {
Self {
track: None,
scroller: Scroller::Color(Rgba::new(128, 128, 128, 255)),
}
}
}
#[derive(Clone, Copy)]
pub enum Track {
Color(Rgba<u8>),
Image(image::Handle, Rgba<u8>),
}
#[derive(Clone, Copy)]
pub enum Scroller {
Color(Rgba<u8>),
Image {
ends: image::Handle,
mid: image::Handle,
color: Rgba<u8>,
},
}

View File

@ -0,0 +1,53 @@
use super::super::super::widget::image;
use vek::Rgba;
#[derive(Clone, Copy)]
pub struct Style {
pub cursor: Cursor,
pub bar: Bar,
pub labels: bool,
pub cursor_size: (u16, u16),
pub bar_height: u16,
}
impl Default for Style {
fn default() -> Self {
Self {
cursor: Cursor::Color(Rgba::new(0.5, 0.5, 0.5, 1.0)),
bar: Bar::Color(Rgba::new(0.5, 0.5, 0.5, 1.0)),
labels: false,
cursor_size: (8, 16),
bar_height: 6,
}
}
}
#[derive(Clone, Copy)]
pub enum Cursor {
Color(Rgba<f32>),
Image(image::Handle, Rgba<u8>),
}
#[derive(Clone, Copy)]
pub enum Bar {
Color(Rgba<f32>),
Image(image::Handle, Rgba<u8>, u16),
}
impl Style {
pub fn images(
cursor: image::Handle,
bar: image::Handle,
bar_pad: u16,
cursor_size: (u16, u16),
bar_height: u16,
) -> Self {
Self {
cursor: Cursor::Image(cursor, Rgba::white()),
bar: Bar::Image(bar, Rgba::white(), bar_pad),
labels: false,
cursor_size,
bar_height,
}
}
}

View File

@ -0,0 +1,23 @@
use super::super::{
super::widget::{aspect_ratio_container, image},
IcedRenderer,
};
use iced::{Element, Layout, Point, Rectangle};
impl aspect_ratio_container::Renderer for IcedRenderer {
type ImageHandle = image::Handle;
fn dimensions(&self, handle: &Self::ImageHandle) -> (u32, u32) { self.image_dims(*handle) }
fn draw<M>(
&mut self,
defaults: &Self::Defaults,
_bounds: Rectangle,
cursor_position: Point,
viewport: &Rectangle,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
content.draw(self, defaults, content_layout, cursor_position, viewport)
}
}

View File

@ -0,0 +1,30 @@
use super::super::{super::widget::background_container, IcedRenderer, Primitive};
use iced::{Element, Layout, Point, Rectangle};
impl background_container::Renderer for IcedRenderer {
fn draw<M, B>(
&mut self,
defaults: &Self::Defaults,
background: &B,
background_layout: Layout<'_>,
viewport: &Rectangle,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output
where
B: background_container::Background<Self>,
{
let back_primitive = background
.draw(self, defaults, background_layout, cursor_position, viewport)
.0;
let (content_primitive, mouse_interaction) =
content.draw(self, defaults, content_layout, cursor_position, viewport);
(
Primitive::Group {
primitives: vec![back_primitive, content_primitive],
},
mouse_interaction,
)
}
}

View File

@ -0,0 +1,66 @@
use super::super::{super::Rotation, style, Defaults, IcedRenderer, Primitive};
use iced::{button, mouse, Element, Layout, Point, Rectangle};
impl button::Renderer for IcedRenderer {
// TODO: what if this gets large enough to not be copied around?
type Style = style::button::Style;
const DEFAULT_PADDING: u16 = 0;
fn draw<M>(
&mut self,
_defaults: &Self::Defaults,
bounds: Rectangle,
cursor_position: Point,
is_disabled: bool,
is_pressed: bool,
style: &Self::Style,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
let (maybe_image, text_color) = if is_disabled {
style.disabled()
} else if is_mouse_over {
if is_pressed {
style.pressed()
} else {
style.hovered()
}
} else {
style.active()
};
let (content, _) = content.draw(
self,
&Defaults { text_color },
content_layout,
cursor_position,
&bounds,
);
let primitive = if let Some((handle, color)) = maybe_image {
let background = Primitive::Image {
handle: (handle, Rotation::None),
bounds,
color,
source_rect: None,
};
Primitive::Group {
primitives: vec![background, content],
}
} else {
content
};
let mouse_interaction = if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
};
(primitive, mouse_interaction)
}
}

View File

@ -0,0 +1,35 @@
use super::super::{IcedRenderer, Primitive};
use iced::{column, mouse, Element, Layout, Point, Rectangle};
impl column::Renderer for IcedRenderer {
fn draw<M>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, M, Self>],
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
(
Primitive::Group {
primitives: content
.iter()
.zip(layout.children())
.map(|(child, layout)| {
let (primitive, new_mouse_interaction) =
child.draw(self, defaults, layout, cursor_position, viewport);
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
}
primitive
})
.collect(),
},
mouse_interaction,
)
}
}

Some files were not shown because too many files have changed in this diff Show More