From f5ba37cf5f1539d54a249f1f6055b70ba84d6e47 Mon Sep 17 00:00:00 2001 From: vE5li Date: Thu, 14 Mar 2024 17:19:51 +0100 Subject: [PATCH] Move interface code to a seperate crate --- .gitignore | 11 +- Cargo.lock | 311 +++---- Cargo.toml | 48 +- korangar/Cargo.toml | 43 + .../{ => archive}/data/WenQuanYiMicroHei.ttf | Bin korangar/{ => archive}/data/icon.png | Bin korangar/{ => archive}/data/model/missing.rsm | Bin korangar/archive/data/sclientinfo.xml | 21 + .../{ => archive}/data/sprite/npc/missing.act | Bin .../{ => archive}/data/sprite/npc/missing.spr | Bin korangar/{ => archive}/data/texture/0.png | Bin korangar/{ => archive}/data/texture/1.png | Bin korangar/{ => archive}/data/texture/2.png | Bin korangar/{ => archive}/data/texture/3.png | Bin korangar/{ => archive}/data/texture/4.png | Bin korangar/{ => archive}/data/texture/5.png | Bin korangar/{ => archive}/data/texture/6.png | Bin .../data/texture/checked_box.png | Bin .../data/texture/collapsed_arrow.png | Bin .../{ => archive}/data/texture/diagonal.png | Bin .../{ => archive}/data/texture/effect.png | Bin .../{ => archive}/data/texture/entity.png | Bin .../data/texture/expanded_arrow.png | Bin korangar/{ => archive}/data/texture/font.png | Bin korangar/{ => archive}/data/texture/goal.png | Bin korangar/{ => archive}/data/texture/light.png | Bin .../{ => archive}/data/texture/missing.bmp | Bin .../{ => archive}/data/texture/missing.png | Bin .../{ => archive}/data/texture/missing.tga | Bin .../data/texture/model/missing.bmp | Bin .../{ => archive}/data/texture/object.png | Bin .../{ => archive}/data/texture/particle.png | Bin korangar/{ => archive}/data/texture/sound.png | Bin .../{ => archive}/data/texture/straight.png | Bin .../data/texture/unchecked_box.png | Bin {src => korangar/src}/debug/logging/colors.rs | 0 {src => korangar/src}/debug/logging/mod.rs | 0 {src => korangar/src}/debug/logging/print.rs | 0 {src => korangar/src}/debug/logging/stack.rs | 0 .../src}/debug/logging/symbols.rs | 0 {src => korangar/src}/debug/logging/timer.rs | 0 {src => korangar/src}/debug/mod.rs | 2 +- .../src}/debug/profiling/measurement.rs | 0 {src => korangar/src}/debug/profiling/mod.rs | 12 +- .../src}/debug/profiling/ring_buffer.rs | 0 .../src}/debug/profiling/statistics.rs | 0 .../src}/graphics/cameras/debug.rs | 2 +- {src => korangar/src}/graphics/cameras/mod.rs | 2 +- .../src}/graphics/cameras/player.rs | 2 +- .../src}/graphics/cameras/shadow.rs | 2 +- .../src}/graphics/cameras/start.rs | 2 +- {src => korangar/src}/graphics/color.rs | 4 +- {src => korangar/src}/graphics/memory.rs | 2 +- {src => korangar/src}/graphics/mod.rs | 0 .../src}/graphics/particles/mod.rs | 4 +- .../deferred/ambient/fragment_shader.glsl | 0 .../renderers/deferred/ambient/mod.rs | 2 +- .../deferred/ambient/vertex_shader.glsl | 0 .../deferred/box/fragment_shader.glsl | 0 .../graphics/renderers/deferred/box/mod.rs | 2 +- .../renderers/deferred/box/vertex_shader.glsl | 0 .../deferred/buffer/fragment_shader.glsl | 0 .../graphics/renderers/deferred/buffer/mod.rs | 2 +- .../deferred/buffer/vertex_shader.glsl | 0 .../deferred/directional/fragment_shader.glsl | 0 .../renderers/deferred/directional/mod.rs | 2 +- .../deferred/directional/vertex_shader.glsl | 0 .../deferred/effect/fragment_shader.glsl | 0 .../graphics/renderers/deferred/effect/mod.rs | 10 +- .../deferred/effect/vertex_shader.glsl | 0 .../deferred/entity/fragment_shader.glsl | 0 .../graphics/renderers/deferred/entity/mod.rs | 2 +- .../deferred/entity/vertex_shader.glsl | 0 .../deferred/geometry/fragment_shader.glsl | 0 .../renderers/deferred/geometry/mod.rs | 2 +- .../deferred/geometry/vertex_shader.glsl | 0 .../deferred/indicator/fragment_shader.glsl | 0 .../renderers/deferred/indicator/mod.rs | 2 +- .../deferred/indicator/vertex_shader.glsl | 0 .../src}/graphics/renderers/deferred/mod.rs | 13 +- .../deferred/overlay/fragment_shader.glsl | 0 .../renderers/deferred/overlay/mod.rs | 2 +- .../deferred/overlay/vertex_shader.glsl | 0 .../deferred/point/fragment_shader.glsl | 0 .../graphics/renderers/deferred/point/mod.rs | 2 +- .../deferred/point/vertex_shader.glsl | 0 .../deferred/rectangle/fragment_shader.glsl | 0 .../renderers/deferred/rectangle/mod.rs | 4 +- .../deferred/rectangle/vertex_shader.glsl | 0 .../deferred/sprite/fragment_shader.glsl | 0 .../graphics/renderers/deferred/sprite/mod.rs | 4 +- .../deferred/sprite/vertex_shader.glsl | 0 .../deferred/water/fragment_shader.glsl | 0 .../graphics/renderers/deferred/water/mod.rs | 2 +- .../deferred/water/vertex_shader.glsl | 0 .../deferred/water_light/fragment_shader.glsl | 0 .../renderers/deferred/water_light/mod.rs | 2 +- .../deferred/water_light/vertex_shader.glsl | 0 .../src}/graphics/renderers/image.rs | 0 .../src}/graphics/renderers/interface/mod.rs | 101 +-- .../interface/rectangle/fragment_shader.glsl | 0 .../renderers/interface/rectangle/mod.rs | 4 +- .../interface/rectangle/vertex_shader.glsl | 0 .../interface/sprite/fragment_shader.glsl | 0 .../renderers/interface/sprite/mod.rs | 4 +- .../interface/sprite/vertex_shader.glsl | 0 .../interface/text/fragment_shader.glsl | 0 .../graphics/renderers/interface/text/mod.rs | 8 +- .../interface/text/vertex_shader.glsl | 0 .../src}/graphics/renderers/mod.rs | 14 +- .../picker/entity/fragment_shader.glsl | 0 .../graphics/renderers/picker/entity/mod.rs | 2 +- .../picker/entity/vertex_shader.glsl | 0 .../picker/geometry/fragment_shader.glsl | 0 .../graphics/renderers/picker/geometry/mod.rs | 2 +- .../picker/geometry/vertex_shader.glsl | 0 .../picker/marker/fragment_shader.glsl | 0 .../graphics/renderers/picker/marker/mod.rs | 4 +- .../picker/marker/vertex_shader.glsl | 0 .../src}/graphics/renderers/picker/mod.rs | 2 +- .../src}/graphics/renderers/picker/target.rs | 0 .../picker/tile/fragment_shader.glsl | 0 .../graphics/renderers/picker/tile/mod.rs | 2 +- .../renderers/picker/tile/vertex_shader.glsl | 0 .../src}/graphics/renderers/pipeline.rs | 0 .../src}/graphics/renderers/sampler.rs | 0 .../src}/graphics/renderers/settings.rs | 2 +- .../shadow/entity/fragment_shader.glsl | 0 .../graphics/renderers/shadow/entity/mod.rs | 2 +- .../shadow/entity/vertex_shader.glsl | 0 .../shadow/geometry/fragment_shader.glsl | 0 .../graphics/renderers/shadow/geometry/mod.rs | 2 +- .../shadow/geometry/vertex_shader.glsl | 0 .../shadow/indicator/fragment_shader.glsl | 0 .../renderers/shadow/indicator/mod.rs | 2 +- .../shadow/indicator/vertex_shader.glsl | 0 .../src}/graphics/renderers/shadow/mod.rs | 0 .../src}/graphics/renderers/swapchain.rs | 4 +- {src => korangar/src}/graphics/settings.rs | 5 +- {src => korangar/src}/graphics/smoothed.rs | 0 {src => korangar/src}/graphics/transform.rs | 2 +- .../src}/graphics/vertices/mod.rs | 0 .../src}/graphics/vertices/model.rs | 0 .../src}/graphics/vertices/native.rs | 0 .../src}/graphics/vertices/tile.rs | 0 .../src}/graphics/vertices/water.rs | 0 {src => korangar/src}/input/event.rs | 83 +- {src => korangar/src}/input/key.rs | 0 {src => korangar/src}/input/mod.rs | 192 ++--- {src => korangar/src}/input/mode.rs | 17 +- korangar/src/interface/application.rs | 362 ++++++++ {src => korangar/src}/interface/cursor/mod.rs | 28 +- korangar/src/interface/dialog.rs | 77 ++ .../elements/containers/character.rs | 93 ++- .../interface/elements/containers/dialog.rs | 59 +- .../elements/containers/equipment.rs | 80 +- .../interface/elements/containers/friends.rs | 182 ++++ .../interface/elements/containers/hotbar.rs | 67 +- .../elements/containers/inventory.rs | 83 +- .../src/interface/elements/containers/mod.rs | 19 + .../interface/elements/containers/packet.rs | 117 ++- .../elements/containers/skill_tree.rs | 81 +- .../elements/miscellanious/chat/builder.rs | 13 +- .../elements/miscellanious/chat/mod.rs | 57 +- .../interface/elements/miscellanious/item.rs | 68 +- .../interface/elements/miscellanious/mod.rs | 7 + .../interface/elements/miscellanious/skill.rs | 65 +- .../src}/interface/elements/mod.rs | 7 - .../src}/interface/elements/mutable.rs | 15 +- .../src}/interface/elements/mutable_range.rs | 33 +- .../interface/elements/profiler/colors.rs | 0 .../src}/interface/elements/profiler/frame.rs | 57 +- .../interface/elements/profiler/inspector.rs | 86 +- .../src}/interface/elements/profiler/mod.rs | 0 .../src/interface/elements/values}/array.rs | 36 +- .../src/interface/elements/values}/color.rs | 37 +- .../src/interface/elements/values}/mod.rs | 0 .../src/interface/elements/values}/number.rs | 36 +- .../src}/interface/elements/wrappers/mod.rs | 0 .../interface/elements/wrappers/mutable.rs | 12 +- .../elements/wrappers/mutable_range.rs | 14 +- .../src/interface/layout.rs | 3 +- korangar/src/interface/mod.rs | 9 + korangar/src/interface/resource.rs | 49 ++ korangar/src/interface/theme/actions.rs | 42 + .../src/interface/theme/mod.rs | 661 +++++++++++---- .../src/interface/windows/account/login.rs | 236 ++++++ .../src}/interface/windows/account/mod.rs | 0 .../windows/account/select_server.rs | 23 +- .../src}/interface/windows/cache.rs | 43 +- .../interface/windows/character/creation.rs | 32 +- .../interface/windows/character/equipment.rs | 43 + .../src/interface/windows/character/hotbar.rs | 42 + .../interface/windows/character/inventory.rs | 43 + .../src}/interface/windows/character/mod.rs | 0 .../interface/windows/character/overview.rs | 22 +- .../interface/windows/character/selection.rs | 48 ++ .../interface/windows/character/skill_tree.rs | 47 ++ .../src}/interface/windows/debug/commands.rs | 36 +- .../src/interface/windows/debug/inspector.rs | 37 + .../src}/interface/windows/debug/maps.rs | 22 +- .../src}/interface/windows/debug/mod.rs | 0 .../src}/interface/windows/debug/packet.rs | 45 +- .../src/interface/windows/debug/profiler.rs | 159 ++++ .../src}/interface/windows/debug/time.rs | 23 +- .../src}/interface/windows/friends/list.rs | 31 +- .../src}/interface/windows/friends/mod.rs | 0 .../src}/interface/windows/friends/request.rs | 19 +- .../src}/interface/windows/generic/chat.rs | 35 +- .../src/interface/windows/generic/dialog.rs | 52 ++ .../src/interface/windows/generic/error.rs | 39 + .../src}/interface/windows/generic/menu.rs | 40 +- .../src}/interface/windows/generic/mod.rs | 0 korangar/src/interface/windows/mod.rs | 19 + .../src}/interface/windows/mutable/array.rs | 23 +- .../src/interface/windows/mutable/color.rs | 46 + .../src}/interface/windows/mutable/mod.rs | 0 .../src/interface/windows/mutable/number.rs | 43 + .../src/interface/windows/settings/audio.rs | 36 + .../interface/windows/settings/graphics.rs | 90 ++ .../src}/interface/windows/settings/mod.rs | 0 .../src/interface/windows/settings/render.rs | 144 ++++ {src => korangar/src}/inventory/hotbar.rs | 16 +- {src => korangar/src}/inventory/mod.rs | 9 +- {src => korangar/src}/inventory/skills.rs | 7 +- {src => korangar/src}/loaders/action/mod.rs | 12 +- .../src}/loaders/archive/folder/mod.rs | 0 {src => korangar/src}/loaders/archive/mod.rs | 0 .../src}/loaders/archive/native/assettable.rs | 2 +- .../src}/loaders/archive/native/builder.rs | 0 .../loaders/archive/native/filetablerow.rs | 2 +- .../src}/loaders/archive/native/header.rs | 2 +- .../src}/loaders/archive/native/mod.rs | 0 {src => korangar/src}/loaders/effect/mod.rs | 3 +- {src => korangar/src}/loaders/font/mod.rs | 100 ++- .../src}/loaders/gamefile/list.rs | 4 +- {src => korangar/src}/loaders/gamefile/mod.rs | 0 {src => korangar/src}/loaders/map/data.rs | 3 +- {src => korangar/src}/loaders/map/mod.rs | 0 {src => korangar/src}/loaders/map/resource.rs | 3 +- {src => korangar/src}/loaders/map/vertices.rs | 0 {src => korangar/src}/loaders/mod.rs | 2 +- {src => korangar/src}/loaders/model/mod.rs | 12 +- {src => korangar/src}/loaders/script/mod.rs | 0 .../src}/loaders/server/client_info.rs | 0 {src => korangar/src}/loaders/server/mod.rs | 0 {src => korangar/src}/loaders/sprite/mod.rs | 3 +- {src => korangar/src}/loaders/texture/mod.rs | 0 {src => korangar/src}/loaders/version.rs | 0 {src => korangar/src}/main.rs | 400 +++++---- {src => korangar/src}/network/login.rs | 0 {src => korangar/src}/network/mod.rs | 76 +- {src => korangar/src}/system/mod.rs | 0 {src => korangar/src}/system/timer.rs | 2 +- {src => korangar/src}/system/vulkan.rs | 0 {src => korangar/src}/world/effect/lookup.rs | 4 +- {src => korangar/src}/world/effect/mod.rs | 4 +- {src => korangar/src}/world/entity/mod.rs | 21 +- {src => korangar/src}/world/light/mod.rs | 4 +- {src => korangar/src}/world/map/mod.rs | 15 +- {src => korangar/src}/world/map/tile.rs | 1 - {src => korangar/src}/world/mod.rs | 0 {src => korangar/src}/world/model/mod.rs | 2 +- {src => korangar/src}/world/model/node.rs | 2 +- {src => korangar/src}/world/object/mod.rs | 2 +- {src => korangar/src}/world/sound/mod.rs | 4 +- korangar_interface/Cargo.toml | 17 + korangar_interface/README.md | 5 + korangar_interface/src/application.rs | 477 +++++++++++ .../src}/builder.rs | 0 korangar_interface/src/elements/base.rs | 354 ++++++++ .../src}/elements/buttons/close/builder.rs | 6 +- .../src/elements/buttons/close/mod.rs | 81 ++ .../src/elements/buttons/default/builder.rs | 160 ++++ .../src/elements/buttons/default/mod.rs | 117 +++ .../src}/elements/buttons/drag/builder.rs | 24 +- .../src/elements/buttons/drag/mod.rs | 83 ++ .../src}/elements/buttons/mod.rs | 0 .../src/elements/buttons/state/builder.rs | 137 +++ .../src/elements/buttons/state/mod.rs | 110 +++ .../src/elements/containers/default.rs | 109 +++ .../src/elements/containers/expandable.rs | 225 +++++ .../src}/elements/containers/mod.rs | 142 ++-- .../src/elements/containers/scroll.rs | 170 ++++ .../src/elements/miscellanious/headline.rs | 72 ++ .../elements/miscellanious/input/builder.rs | 90 +- .../src/elements/miscellanious/input/mod.rs | 163 ++++ .../src}/elements/miscellanious/mod.rs | 6 - .../src/elements/miscellanious/picklist.rs | 214 +++++ .../src/elements/miscellanious/slider.rs | 144 ++++ .../elements/miscellanious/static_label.rs | 80 ++ .../src/elements/miscellanious/text.rs | 133 +++ korangar_interface/src/elements/mod.rs | 14 + .../src}/elements/prototype.rs | 120 ++- korangar_interface/src/elements/value.rs | 68 ++ korangar_interface/src/event/action.rs | 26 + .../src}/event/change.rs | 0 korangar_interface/src/event/hover.rs | 11 + .../src}/event/mod.rs | 4 - .../src}/layout/bound.rs | 164 ++-- .../src}/layout/dimension.rs | 40 +- .../src}/layout/mod.rs | 2 - korangar_interface/src/layout/resolver.rs | 234 ++++++ korangar_interface/src/lib.rs | 544 ++++++++++++ korangar_interface/src/settings.rs | 0 korangar_interface/src/state.rs | 518 ++++++++++++ korangar_interface/src/theme.rs | 215 +++++ .../src}/windows/builder.rs | 127 ++- korangar_interface/src/windows/mod.rs | 286 +++++++ korangar_interface/src/windows/prototype.rs | 13 + .../Cargo.toml | 2 +- {procedural => korangar_procedural}/README.md | 0 .../src/bound.rs | 14 +- .../src/lib.rs | 0 korangar_procedural/src/prototype/element.rs | 95 +++ .../src/prototype/helper.rs | 3 +- .../src/prototype/mod.rs | 0 korangar_procedural/src/prototype/window.rs | 77 ++ .../src/toggle.rs | 0 .../src/utils.rs | 0 procedural/src/prototype/element.rs | 59 -- procedural/src/prototype/window.rs | 41 - ragnarok_bytes/Cargo.toml | 2 + ragnarok_bytes/src/lib.rs | 6 + ragnarok_procedural/README.md | 4 + src/interface/elements/base.rs | 345 -------- src/interface/elements/buttons/close/mod.rs | 74 -- .../elements/buttons/default/builder.rs | 130 --- src/interface/elements/buttons/default/mod.rs | 108 --- src/interface/elements/buttons/drag/mod.rs | 77 -- .../elements/buttons/state/builder.rs | 108 --- src/interface/elements/buttons/state/mod.rs | 101 --- src/interface/elements/containers/default.rs | 101 --- .../elements/containers/expandable.rs | 213 ----- src/interface/elements/containers/friends.rs | 166 ---- src/interface/elements/containers/scroll.rs | 154 ---- .../elements/miscellanious/headline.rs | 61 -- .../elements/miscellanious/input/mod.rs | 151 ---- .../elements/miscellanious/picklist.rs | 196 ----- .../elements/miscellanious/slider.rs | 120 --- .../elements/miscellanious/static_label.rs | 65 -- src/interface/elements/miscellanious/text.rs | 105 --- src/interface/elements/values/color.rs | 65 -- src/interface/elements/values/mod.rs | 7 - src/interface/elements/values/quaternion.rs | 69 -- src/interface/elements/values/string.rs | 53 -- src/interface/event/action.rs | 23 - src/interface/event/hover.rs | 7 - src/interface/event/item.rs | 15 - src/interface/event/skill.rs | 15 - src/interface/layout/resolver.rs | 230 ----- src/interface/mod.rs | 789 ------------------ src/interface/provider.rs | 12 - src/interface/settings.rs | 118 --- src/interface/state.rs | 240 ------ src/interface/windows/account/login.rs | 269 ------ src/interface/windows/character/equipment.rs | 31 - src/interface/windows/character/hotbar.rs | 30 - src/interface/windows/character/inventory.rs | 31 - src/interface/windows/character/selection.rs | 36 - src/interface/windows/character/skill_tree.rs | 31 - src/interface/windows/debug/inspector.rs | 27 - src/interface/windows/debug/profiler.rs | 73 -- src/interface/windows/generic/dialog.rs | 38 - src/interface/windows/generic/error.rs | 29 - src/interface/windows/mod.rs | 303 ------- src/interface/windows/mutable/color.rs | 35 - src/interface/windows/mutable/number.rs | 32 - src/interface/windows/prototype.rs | 9 - src/interface/windows/settings/audio.rs | 26 - src/interface/windows/settings/graphics.rs | 61 -- src/interface/windows/settings/render.rs | 129 --- 372 files changed, 9668 insertions(+), 7013 deletions(-) create mode 100644 korangar/Cargo.toml rename korangar/{ => archive}/data/WenQuanYiMicroHei.ttf (100%) rename korangar/{ => archive}/data/icon.png (100%) rename korangar/{ => archive}/data/model/missing.rsm (100%) create mode 100755 korangar/archive/data/sclientinfo.xml rename korangar/{ => archive}/data/sprite/npc/missing.act (100%) rename korangar/{ => archive}/data/sprite/npc/missing.spr (100%) rename korangar/{ => archive}/data/texture/0.png (100%) rename korangar/{ => archive}/data/texture/1.png (100%) rename korangar/{ => archive}/data/texture/2.png (100%) rename korangar/{ => archive}/data/texture/3.png (100%) rename korangar/{ => archive}/data/texture/4.png (100%) rename korangar/{ => archive}/data/texture/5.png (100%) rename korangar/{ => archive}/data/texture/6.png (100%) rename korangar/{ => archive}/data/texture/checked_box.png (100%) rename korangar/{ => archive}/data/texture/collapsed_arrow.png (100%) rename korangar/{ => archive}/data/texture/diagonal.png (100%) rename korangar/{ => archive}/data/texture/effect.png (100%) rename korangar/{ => archive}/data/texture/entity.png (100%) rename korangar/{ => archive}/data/texture/expanded_arrow.png (100%) rename korangar/{ => archive}/data/texture/font.png (100%) rename korangar/{ => archive}/data/texture/goal.png (100%) rename korangar/{ => archive}/data/texture/light.png (100%) rename korangar/{ => archive}/data/texture/missing.bmp (100%) rename korangar/{ => archive}/data/texture/missing.png (100%) rename korangar/{ => archive}/data/texture/missing.tga (100%) rename korangar/{ => archive}/data/texture/model/missing.bmp (100%) rename korangar/{ => archive}/data/texture/object.png (100%) rename korangar/{ => archive}/data/texture/particle.png (100%) rename korangar/{ => archive}/data/texture/sound.png (100%) rename korangar/{ => archive}/data/texture/straight.png (100%) rename korangar/{ => archive}/data/texture/unchecked_box.png (100%) rename {src => korangar/src}/debug/logging/colors.rs (100%) rename {src => korangar/src}/debug/logging/mod.rs (100%) rename {src => korangar/src}/debug/logging/print.rs (100%) rename {src => korangar/src}/debug/logging/stack.rs (100%) rename {src => korangar/src}/debug/logging/symbols.rs (100%) rename {src => korangar/src}/debug/logging/timer.rs (100%) rename {src => korangar/src}/debug/mod.rs (90%) rename {src => korangar/src}/debug/profiling/measurement.rs (100%) rename {src => korangar/src}/debug/profiling/mod.rs (94%) rename {src => korangar/src}/debug/profiling/ring_buffer.rs (100%) rename {src => korangar/src}/debug/profiling/statistics.rs (100%) rename {src => korangar/src}/graphics/cameras/debug.rs (99%) rename {src => korangar/src}/graphics/cameras/mod.rs (96%) rename {src => korangar/src}/graphics/cameras/player.rs (99%) rename {src => korangar/src}/graphics/cameras/shadow.rs (99%) rename {src => korangar/src}/graphics/cameras/start.rs (99%) rename {src => korangar/src}/graphics/color.rs (98%) rename {src => korangar/src}/graphics/memory.rs (99%) rename {src => korangar/src}/graphics/mod.rs (100%) rename {src => korangar/src}/graphics/particles/mod.rs (98%) rename {src => korangar/src}/graphics/renderers/deferred/ambient/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/ambient/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/ambient/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/box/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/box/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/box/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/buffer/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/buffer/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/buffer/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/directional/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/directional/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/directional/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/effect/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/effect/mod.rs (94%) rename {src => korangar/src}/graphics/renderers/deferred/effect/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/entity/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/entity/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/entity/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/geometry/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/geometry/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/geometry/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/indicator/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/indicator/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/indicator/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/mod.rs (98%) rename {src => korangar/src}/graphics/renderers/deferred/overlay/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/overlay/mod.rs (98%) rename {src => korangar/src}/graphics/renderers/deferred/overlay/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/point/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/point/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/point/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/rectangle/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/rectangle/mod.rs (97%) rename {src => korangar/src}/graphics/renderers/deferred/rectangle/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/sprite/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/sprite/mod.rs (98%) rename {src => korangar/src}/graphics/renderers/deferred/sprite/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/water/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/water/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/water/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/water_light/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/deferred/water_light/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/deferred/water_light/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/image.rs (100%) rename {src => korangar/src}/graphics/renderers/interface/mod.rs (76%) rename {src => korangar/src}/graphics/renderers/interface/rectangle/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/interface/rectangle/mod.rs (96%) rename {src => korangar/src}/graphics/renderers/interface/rectangle/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/interface/sprite/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/interface/sprite/mod.rs (97%) rename {src => korangar/src}/graphics/renderers/interface/sprite/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/interface/text/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/interface/text/mod.rs (96%) rename {src => korangar/src}/graphics/renderers/interface/text/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/picker/entity/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/entity/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/picker/entity/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/geometry/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/geometry/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/picker/geometry/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/marker/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/marker/mod.rs (97%) rename {src => korangar/src}/graphics/renderers/picker/marker/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/picker/target.rs (100%) rename {src => korangar/src}/graphics/renderers/picker/tile/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/picker/tile/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/picker/tile/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/pipeline.rs (100%) rename {src => korangar/src}/graphics/renderers/sampler.rs (100%) rename {src => korangar/src}/graphics/renderers/settings.rs (98%) rename {src => korangar/src}/graphics/renderers/shadow/entity/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/shadow/entity/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/shadow/entity/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/shadow/geometry/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/shadow/geometry/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/shadow/geometry/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/shadow/indicator/fragment_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/shadow/indicator/mod.rs (99%) rename {src => korangar/src}/graphics/renderers/shadow/indicator/vertex_shader.glsl (100%) rename {src => korangar/src}/graphics/renderers/shadow/mod.rs (100%) rename {src => korangar/src}/graphics/renderers/swapchain.rs (98%) rename {src => korangar/src}/graphics/settings.rs (93%) rename {src => korangar/src}/graphics/smoothed.rs (100%) rename {src => korangar/src}/graphics/transform.rs (97%) rename {src => korangar/src}/graphics/vertices/mod.rs (100%) rename {src => korangar/src}/graphics/vertices/model.rs (100%) rename {src => korangar/src}/graphics/vertices/native.rs (100%) rename {src => korangar/src}/graphics/vertices/tile.rs (100%) rename {src => korangar/src}/graphics/vertices/water.rs (100%) rename {src => korangar/src}/input/event.rs (59%) rename {src => korangar/src}/input/key.rs (100%) rename {src => korangar/src}/input/mod.rs (79%) rename {src => korangar/src}/input/mode.rs (62%) create mode 100644 korangar/src/interface/application.rs rename {src => korangar/src}/interface/cursor/mod.rs (85%) create mode 100644 korangar/src/interface/dialog.rs rename {src => korangar/src}/interface/elements/containers/character.rs (63%) rename {src => korangar/src}/interface/elements/containers/dialog.rs (61%) rename {src => korangar/src}/interface/elements/containers/equipment.rs (59%) create mode 100644 korangar/src/interface/elements/containers/friends.rs rename {src => korangar/src}/interface/elements/containers/hotbar.rs (56%) rename {src => korangar/src}/interface/elements/containers/inventory.rs (52%) create mode 100644 korangar/src/interface/elements/containers/mod.rs rename {src => korangar/src}/interface/elements/containers/packet.rs (68%) rename {src => korangar/src}/interface/elements/containers/skill_tree.rs (50%) rename {src => korangar/src}/interface/elements/miscellanious/chat/builder.rs (78%) rename {src => korangar/src}/interface/elements/miscellanious/chat/mod.rs (57%) rename {src => korangar/src}/interface/elements/miscellanious/item.rs (51%) create mode 100644 korangar/src/interface/elements/miscellanious/mod.rs rename {src => korangar/src}/interface/elements/miscellanious/skill.rs (57%) rename {src => korangar/src}/interface/elements/mod.rs (76%) rename {src => korangar/src}/interface/elements/mutable.rs (71%) rename {src => korangar/src}/interface/elements/mutable_range.rs (64%) rename {src => korangar/src}/interface/elements/profiler/colors.rs (100%) rename {src => korangar/src}/interface/elements/profiler/frame.rs (69%) rename {src => korangar/src}/interface/elements/profiler/inspector.rs (77%) rename {src => korangar/src}/interface/elements/profiler/mod.rs (100%) rename {src/interface/elements/values/mutable => korangar/src/interface/elements/values}/array.rs (73%) rename {src/interface/elements/values/mutable => korangar/src/interface/elements/values}/color.rs (70%) rename {src/interface/elements/values/mutable => korangar/src/interface/elements/values}/mod.rs (100%) rename {src/interface/elements/values/mutable => korangar/src/interface/elements/values}/number.rs (71%) rename {src => korangar/src}/interface/elements/wrappers/mod.rs (100%) rename {src => korangar/src}/interface/elements/wrappers/mutable.rs (65%) rename {src => korangar/src}/interface/elements/wrappers/mutable_range.rs (70%) rename src/interface/layout/size.rs => korangar/src/interface/layout.rs (99%) create mode 100644 korangar/src/interface/mod.rs create mode 100644 korangar/src/interface/resource.rs create mode 100644 korangar/src/interface/theme/actions.rs rename src/interface/theme.rs => korangar/src/interface/theme/mod.rs (67%) create mode 100644 korangar/src/interface/windows/account/login.rs rename {src => korangar/src}/interface/windows/account/mod.rs (100%) rename {src => korangar/src}/interface/windows/account/select_server.rs (53%) rename {src => korangar/src}/interface/windows/cache.rs (77%) rename {src => korangar/src}/interface/windows/character/creation.rs (55%) create mode 100644 korangar/src/interface/windows/character/equipment.rs create mode 100644 korangar/src/interface/windows/character/hotbar.rs create mode 100644 korangar/src/interface/windows/character/inventory.rs rename {src => korangar/src}/interface/windows/character/mod.rs (100%) rename {src => korangar/src}/interface/windows/character/overview.rs (69%) create mode 100644 korangar/src/interface/windows/character/selection.rs create mode 100644 korangar/src/interface/windows/character/skill_tree.rs rename {src => korangar/src}/interface/windows/debug/commands.rs (81%) create mode 100644 korangar/src/interface/windows/debug/inspector.rs rename {src => korangar/src}/interface/windows/debug/maps.rs (73%) rename {src => korangar/src}/interface/windows/debug/mod.rs (100%) rename {src => korangar/src}/interface/windows/debug/packet.rs (50%) create mode 100644 korangar/src/interface/windows/debug/profiler.rs rename {src => korangar/src}/interface/windows/debug/time.rs (63%) rename {src => korangar/src}/interface/windows/friends/list.rs (53%) rename {src => korangar/src}/interface/windows/friends/mod.rs (100%) rename {src => korangar/src}/interface/windows/friends/request.rs (71%) rename {src => korangar/src}/interface/windows/generic/chat.rs (62%) create mode 100644 korangar/src/interface/windows/generic/dialog.rs create mode 100644 korangar/src/interface/windows/generic/error.rs rename {src => korangar/src}/interface/windows/generic/menu.rs (63%) rename {src => korangar/src}/interface/windows/generic/mod.rs (100%) create mode 100644 korangar/src/interface/windows/mod.rs rename {src => korangar/src}/interface/windows/mutable/array.rs (57%) create mode 100644 korangar/src/interface/windows/mutable/color.rs rename {src => korangar/src}/interface/windows/mutable/mod.rs (100%) create mode 100644 korangar/src/interface/windows/mutable/number.rs create mode 100644 korangar/src/interface/windows/settings/audio.rs create mode 100644 korangar/src/interface/windows/settings/graphics.rs rename {src => korangar/src}/interface/windows/settings/mod.rs (100%) create mode 100644 korangar/src/interface/windows/settings/render.rs rename {src => korangar/src}/inventory/hotbar.rs (70%) rename {src => korangar/src}/inventory/mod.rs (93%) rename {src => korangar/src}/inventory/skills.rs (90%) rename {src => korangar/src}/loaders/action/mod.rs (96%) rename {src => korangar/src}/loaders/archive/folder/mod.rs (100%) rename {src => korangar/src}/loaders/archive/mod.rs (100%) rename {src => korangar/src}/loaders/archive/native/assettable.rs (85%) rename {src => korangar/src}/loaders/archive/native/builder.rs (100%) rename {src => korangar/src}/loaders/archive/native/filetablerow.rs (88%) rename {src => korangar/src}/loaders/archive/native/header.rs (91%) rename {src => korangar/src}/loaders/archive/native/mod.rs (100%) rename {src => korangar/src}/loaders/effect/mod.rs (99%) rename {src => korangar/src}/loaders/font/mod.rs (76%) rename {src => korangar/src}/loaders/gamefile/list.rs (91%) rename {src => korangar/src}/loaders/gamefile/mod.rs (100%) rename {src => korangar/src}/loaders/map/data.rs (98%) rename {src => korangar/src}/loaders/map/mod.rs (100%) rename {src => korangar/src}/loaders/map/resource.rs (98%) rename {src => korangar/src}/loaders/map/vertices.rs (100%) rename {src => korangar/src}/loaders/mod.rs (92%) rename {src => korangar/src}/loaders/model/mod.rs (97%) rename {src => korangar/src}/loaders/script/mod.rs (100%) rename {src => korangar/src}/loaders/server/client_info.rs (100%) rename {src => korangar/src}/loaders/server/mod.rs (100%) rename {src => korangar/src}/loaders/sprite/mod.rs (99%) rename {src => korangar/src}/loaders/texture/mod.rs (100%) rename {src => korangar/src}/loaders/version.rs (100%) rename {src => korangar/src}/main.rs (83%) rename {src => korangar/src}/network/login.rs (100%) rename {src => korangar/src}/network/mod.rs (98%) rename {src => korangar/src}/system/mod.rs (100%) rename {src => korangar/src}/system/timer.rs (98%) rename {src => korangar/src}/system/vulkan.rs (100%) rename {src => korangar/src}/world/effect/lookup.rs (99%) rename {src => korangar/src}/world/effect/mod.rs (91%) rename {src => korangar/src}/world/entity/mod.rs (98%) rename {src => korangar/src}/world/light/mod.rs (92%) rename {src => korangar/src}/world/map/mod.rs (97%) rename {src => korangar/src}/world/map/tile.rs (97%) rename {src => korangar/src}/world/mod.rs (100%) rename {src => korangar/src}/world/model/mod.rs (98%) rename {src => korangar/src}/world/model/node.rs (99%) rename {src => korangar/src}/world/object/mod.rs (96%) rename {src => korangar/src}/world/sound/mod.rs (91%) create mode 100644 korangar_interface/Cargo.toml create mode 100644 korangar_interface/README.md create mode 100644 korangar_interface/src/application.rs rename {src/interface => korangar_interface/src}/builder.rs (100%) create mode 100644 korangar_interface/src/elements/base.rs rename {src/interface => korangar_interface/src}/elements/buttons/close/builder.rs (80%) create mode 100644 korangar_interface/src/elements/buttons/close/mod.rs create mode 100644 korangar_interface/src/elements/buttons/default/builder.rs create mode 100644 korangar_interface/src/elements/buttons/default/mod.rs rename {src/interface => korangar_interface/src}/elements/buttons/drag/builder.rs (73%) create mode 100644 korangar_interface/src/elements/buttons/drag/mod.rs rename {src/interface => korangar_interface/src}/elements/buttons/mod.rs (100%) create mode 100644 korangar_interface/src/elements/buttons/state/builder.rs create mode 100644 korangar_interface/src/elements/buttons/state/mod.rs create mode 100644 korangar_interface/src/elements/containers/default.rs create mode 100644 korangar_interface/src/elements/containers/expandable.rs rename {src/interface => korangar_interface/src}/elements/containers/mod.rs (76%) create mode 100644 korangar_interface/src/elements/containers/scroll.rs create mode 100644 korangar_interface/src/elements/miscellanious/headline.rs rename {src/interface => korangar_interface/src}/elements/miscellanious/input/builder.rs (51%) create mode 100644 korangar_interface/src/elements/miscellanious/input/mod.rs rename {src/interface => korangar_interface/src}/elements/miscellanious/mod.rs (69%) create mode 100644 korangar_interface/src/elements/miscellanious/picklist.rs create mode 100644 korangar_interface/src/elements/miscellanious/slider.rs create mode 100644 korangar_interface/src/elements/miscellanious/static_label.rs create mode 100644 korangar_interface/src/elements/miscellanious/text.rs create mode 100644 korangar_interface/src/elements/mod.rs rename {src/interface => korangar_interface/src}/elements/prototype.rs (59%) create mode 100644 korangar_interface/src/elements/value.rs create mode 100644 korangar_interface/src/event/action.rs rename {src/interface => korangar_interface/src}/event/change.rs (100%) create mode 100644 korangar_interface/src/event/hover.rs rename {src/interface => korangar_interface/src}/event/mod.rs (54%) rename {src/interface => korangar_interface/src}/layout/bound.rs (52%) rename {src/interface => korangar_interface/src}/layout/dimension.rs (67%) rename {src/interface => korangar_interface/src}/layout/mod.rs (59%) create mode 100644 korangar_interface/src/layout/resolver.rs create mode 100644 korangar_interface/src/lib.rs create mode 100644 korangar_interface/src/settings.rs create mode 100644 korangar_interface/src/state.rs create mode 100644 korangar_interface/src/theme.rs rename {src/interface => korangar_interface/src}/windows/builder.rs (53%) create mode 100644 korangar_interface/src/windows/mod.rs create mode 100644 korangar_interface/src/windows/prototype.rs rename {procedural => korangar_procedural}/Cargo.toml (85%) rename {procedural => korangar_procedural}/README.md (100%) rename {procedural => korangar_procedural}/src/bound.rs (91%) rename {procedural => korangar_procedural}/src/lib.rs (100%) create mode 100644 korangar_procedural/src/prototype/element.rs rename {procedural => korangar_procedural}/src/prototype/helper.rs (92%) rename {procedural => korangar_procedural}/src/prototype/mod.rs (100%) create mode 100644 korangar_procedural/src/prototype/window.rs rename {procedural => korangar_procedural}/src/toggle.rs (100%) rename {procedural => korangar_procedural}/src/utils.rs (100%) delete mode 100644 procedural/src/prototype/element.rs delete mode 100644 procedural/src/prototype/window.rs delete mode 100644 src/interface/elements/base.rs delete mode 100644 src/interface/elements/buttons/close/mod.rs delete mode 100644 src/interface/elements/buttons/default/builder.rs delete mode 100644 src/interface/elements/buttons/default/mod.rs delete mode 100644 src/interface/elements/buttons/drag/mod.rs delete mode 100644 src/interface/elements/buttons/state/builder.rs delete mode 100644 src/interface/elements/buttons/state/mod.rs delete mode 100644 src/interface/elements/containers/default.rs delete mode 100644 src/interface/elements/containers/expandable.rs delete mode 100644 src/interface/elements/containers/friends.rs delete mode 100644 src/interface/elements/containers/scroll.rs delete mode 100644 src/interface/elements/miscellanious/headline.rs delete mode 100644 src/interface/elements/miscellanious/input/mod.rs delete mode 100644 src/interface/elements/miscellanious/picklist.rs delete mode 100644 src/interface/elements/miscellanious/slider.rs delete mode 100644 src/interface/elements/miscellanious/static_label.rs delete mode 100644 src/interface/elements/miscellanious/text.rs delete mode 100644 src/interface/elements/values/color.rs delete mode 100644 src/interface/elements/values/mod.rs delete mode 100644 src/interface/elements/values/quaternion.rs delete mode 100644 src/interface/elements/values/string.rs delete mode 100644 src/interface/event/action.rs delete mode 100644 src/interface/event/hover.rs delete mode 100644 src/interface/event/item.rs delete mode 100644 src/interface/event/skill.rs delete mode 100644 src/interface/layout/resolver.rs delete mode 100644 src/interface/mod.rs delete mode 100644 src/interface/provider.rs delete mode 100644 src/interface/settings.rs delete mode 100644 src/interface/state.rs delete mode 100644 src/interface/windows/account/login.rs delete mode 100644 src/interface/windows/character/equipment.rs delete mode 100644 src/interface/windows/character/hotbar.rs delete mode 100644 src/interface/windows/character/inventory.rs delete mode 100644 src/interface/windows/character/selection.rs delete mode 100644 src/interface/windows/character/skill_tree.rs delete mode 100644 src/interface/windows/debug/inspector.rs delete mode 100644 src/interface/windows/debug/profiler.rs delete mode 100644 src/interface/windows/generic/dialog.rs delete mode 100644 src/interface/windows/generic/error.rs delete mode 100644 src/interface/windows/mod.rs delete mode 100644 src/interface/windows/mutable/color.rs delete mode 100644 src/interface/windows/mutable/number.rs delete mode 100644 src/interface/windows/prototype.rs delete mode 100644 src/interface/windows/settings/audio.rs delete mode 100644 src/interface/windows/settings/graphics.rs delete mode 100644 src/interface/windows/settings/render.rs diff --git a/.gitignore b/.gitignore index a67e63f6..bb64e494 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ # Rust target/ -procedural/target # Korangar -client/ -data.grf -rdata.grf -lua_files/ -lua_files.grf +korangar/client/ +korangar/data.grf +korangar/rdata.grf +korangar/lua_files/ +korangar/lua_files.grf diff --git a/Cargo.lock b/Cargo.lock index 245ef9c9..ac9daf25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,9 +26,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom 0.2.12", @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "base64" @@ -156,9 +156,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -193,28 +193,28 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" dependencies = [ "jobserver", "libc", @@ -272,16 +272,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -423,7 +423,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading 0.8.3", ] [[package]] @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", @@ -553,9 +553,9 @@ dependencies = [ [[package]] name = "half" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" dependencies = [ "bytemuck", "cfg-if", @@ -582,9 +582,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" @@ -620,9 +620,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.8" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", @@ -648,9 +648,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -688,9 +688,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni-sys" @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -729,22 +729,22 @@ dependencies = [ name = "korangar" version = "0.1.0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytemuck", "cgmath", "chrono", "collision", "derive-new", "image", + "korangar_interface", + "korangar_procedural", "lazy_static", "lunify", "mlua", "num", "option-ext", "pathfinding", - "procedural", "ragnarok_bytes", - "ragnarok_procedural", "rand 0.8.5", "random_color", "rayon", @@ -761,6 +761,27 @@ dependencies = [ "yazi", ] +[[package]] +name = "korangar_interface" +version = "0.1.0" +dependencies = [ + "bitflags 2.5.0", + "cgmath", + "korangar_procedural", + "num", + "option-ext", + "serde", +] + +[[package]] +name = "korangar_procedural" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -791,12 +812,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.4", ] [[package]] @@ -805,7 +826,7 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall 0.4.1", ] @@ -834,9 +855,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lua-src" @@ -908,9 +929,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -1112,7 +1133,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] @@ -1234,15 +1255,15 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "png" -version = "0.17.11" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -1269,22 +1290,13 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] -[[package]] -name = "procedural" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "qoi" version = "0.4.1" @@ -1308,27 +1320,16 @@ name = "ragnarok_bytes" version = "0.1.0" dependencies = [ "cgmath", + "ragnarok_procedural", ] -[[package]] -name = "ragnarok_packets" -version = "0.1.0" - [[package]] name = "ragnarok_procedural" version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", -] - -[[package]] -name = "ragnarok_types" -version = "0.1.0" -dependencies = [ - "ragnarok_bytes", - "ragnarok_procedural", + "syn 2.0.55", ] [[package]] @@ -1429,9 +1430,9 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -1467,9 +1468,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1479,9 +1480,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -1501,7 +1502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.4.2", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -1523,11 +1524,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1551,9 +1552,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -1591,9 +1592,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -1612,20 +1613,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1670,9 +1671,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" @@ -1721,9 +1722,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -1732,29 +1733,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -1808,7 +1809,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -1864,8 +1865,8 @@ dependencies = [ "crossbeam-queue", "half", "heck", - "indexmap 2.2.3", - "libloading 0.8.1", + "indexmap 2.2.6", + "libloading 0.8.3", "objc", "once_cell", "parking_lot", @@ -1902,7 +1903,7 @@ dependencies = [ "proc-macro2", "quote", "shaderc", - "syn 2.0.48", + "syn 2.0.55", "vulkano", ] @@ -1920,9 +1921,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1942,9 +1943,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1952,24 +1953,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1977,22 +1978,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wayland-client" @@ -2069,9 +2070,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -2132,7 +2133,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -2159,7 +2160,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -2194,17 +2195,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -2221,9 +2222,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -2239,9 +2240,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -2257,9 +2258,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -2275,9 +2276,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -2293,9 +2294,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -2311,9 +2312,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -2329,9 +2330,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winit" @@ -2370,9 +2371,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.39" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -2429,7 +2430,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.55", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e14fb039..3f1e7917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,13 @@ [workspace] resolver = "2" -members = ["ragnarok_*", "procedural"] +members = ["korangar", "ragnarok_*", "korangar_*"] [workspace.dependencies] +bitflags = "2.4.2" cgmath = { version = "0.18", features = ["serde"] } +korangar_interface = { path = "korangar_interface" } +num = "0.4.1" +korangar_procedural = { path = "korangar_procedural" } ragnarok_bytes = { path = "ragnarok_bytes", features = ["cgmath"] } ragnarok_procedural = { path = "ragnarok_procedural" } - -[package] -name = "korangar" -version = "0.1.0" -edition = "2021" - -[dependencies] -bitflags = "2.4.2" -bytemuck = { version = "1.9", features = ["derive", "extern_crate_std", "min_const_generics"] } -cgmath = { workspace = true, features = ["serde"] } -chrono = "0.4" -collision = { git = "https://github.com/rustgd/collision-rs.git" } -derive-new = "0.5" -image = "0.24.2" -lazy_static = { version = "1.4.0", optional = true } -lunify = "1.1.0" -mlua = { version = "0.8", features = ["lua51", "vendored"] } -num = "*" -option-ext = "0.2.0" -pathfinding = "2.2.2" -procedural = { path = "procedural" } -ragnarok_bytes = { workspace = true, features = ["cgmath"] } -ragnarok_procedural = { workspace = true } -rand = "0.8.5" -random_color = { version = "0.6.1", optional = true } -rayon = "1.5.3" -ron = "0.8.0" -rusttype = { version = "0.9.2", features = ["gpu_cache"] } serde = "1.0.137" -serde-xml-rs = "0.6.0" -vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } -vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } -vulkano-win = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } -walkdir = "2" -winit = "0.28.7" -xml-rs = "0.8.0" -yazi = "0.1.4" - -[features] -patched_as_folder = [] -debug = ["lazy_static", "random_color"] -unicode = ["debug"] -plain = ["debug"] diff --git a/korangar/Cargo.toml b/korangar/Cargo.toml new file mode 100644 index 00000000..37187332 --- /dev/null +++ b/korangar/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "korangar" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = { workspace = true } +bytemuck = { version = "1.9", features = ["derive", "extern_crate_std", "min_const_generics"] } +cgmath = { workspace = true, features = ["serde"] } +chrono = "0.4" +collision = { git = "https://github.com/rustgd/collision-rs.git" } +derive-new = "0.5" +image = "0.24.2" +korangar_interface = { workspace = true, features = ["serde", "cgmath"] } +lazy_static = { version = "1.4.0", optional = true } +lunify = "1.1.0" +mlua = { version = "0.8", features = ["lua51", "vendored"] } +num = { workspace = true } +# TODO: Remove or move to workspace +option-ext = "0.2.0" +pathfinding = "2.2.2" +korangar_procedural = { workspace = true } +ragnarok_bytes = { workspace = true, features = ["derive", "cgmath"] } +rand = "0.8.5" +random_color = { version = "0.6.1", optional = true } +rayon = "1.5.3" +ron = "0.8.0" +rusttype = { version = "0.9.2", features = ["gpu_cache"] } +serde = { workspace = true } +serde-xml-rs = "0.6.0" +vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } +vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } +vulkano-win = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } +walkdir = "2" +winit = "0.28.7" +xml-rs = "0.8.0" +yazi = "0.1.4" + +[features] +patched_as_folder = [] +debug = ["lazy_static", "random_color"] +unicode = ["debug"] +plain = ["debug"] diff --git a/korangar/data/WenQuanYiMicroHei.ttf b/korangar/archive/data/WenQuanYiMicroHei.ttf similarity index 100% rename from korangar/data/WenQuanYiMicroHei.ttf rename to korangar/archive/data/WenQuanYiMicroHei.ttf diff --git a/korangar/data/icon.png b/korangar/archive/data/icon.png similarity index 100% rename from korangar/data/icon.png rename to korangar/archive/data/icon.png diff --git a/korangar/data/model/missing.rsm b/korangar/archive/data/model/missing.rsm similarity index 100% rename from korangar/data/model/missing.rsm rename to korangar/archive/data/model/missing.rsm diff --git a/korangar/archive/data/sclientinfo.xml b/korangar/archive/data/sclientinfo.xml new file mode 100755 index 00000000..119a405d --- /dev/null +++ b/korangar/archive/data/sclientinfo.xml @@ -0,0 +1,21 @@ + + + Ragnarok Client Information + korea + sakray + + + Korangar + Ragnarok Online + Sakray Server +
49.12.109.207
+ 6900 + 20 + 1 + + + + loading00.jpg + +
+
diff --git a/korangar/data/sprite/npc/missing.act b/korangar/archive/data/sprite/npc/missing.act similarity index 100% rename from korangar/data/sprite/npc/missing.act rename to korangar/archive/data/sprite/npc/missing.act diff --git a/korangar/data/sprite/npc/missing.spr b/korangar/archive/data/sprite/npc/missing.spr similarity index 100% rename from korangar/data/sprite/npc/missing.spr rename to korangar/archive/data/sprite/npc/missing.spr diff --git a/korangar/data/texture/0.png b/korangar/archive/data/texture/0.png similarity index 100% rename from korangar/data/texture/0.png rename to korangar/archive/data/texture/0.png diff --git a/korangar/data/texture/1.png b/korangar/archive/data/texture/1.png similarity index 100% rename from korangar/data/texture/1.png rename to korangar/archive/data/texture/1.png diff --git a/korangar/data/texture/2.png b/korangar/archive/data/texture/2.png similarity index 100% rename from korangar/data/texture/2.png rename to korangar/archive/data/texture/2.png diff --git a/korangar/data/texture/3.png b/korangar/archive/data/texture/3.png similarity index 100% rename from korangar/data/texture/3.png rename to korangar/archive/data/texture/3.png diff --git a/korangar/data/texture/4.png b/korangar/archive/data/texture/4.png similarity index 100% rename from korangar/data/texture/4.png rename to korangar/archive/data/texture/4.png diff --git a/korangar/data/texture/5.png b/korangar/archive/data/texture/5.png similarity index 100% rename from korangar/data/texture/5.png rename to korangar/archive/data/texture/5.png diff --git a/korangar/data/texture/6.png b/korangar/archive/data/texture/6.png similarity index 100% rename from korangar/data/texture/6.png rename to korangar/archive/data/texture/6.png diff --git a/korangar/data/texture/checked_box.png b/korangar/archive/data/texture/checked_box.png similarity index 100% rename from korangar/data/texture/checked_box.png rename to korangar/archive/data/texture/checked_box.png diff --git a/korangar/data/texture/collapsed_arrow.png b/korangar/archive/data/texture/collapsed_arrow.png similarity index 100% rename from korangar/data/texture/collapsed_arrow.png rename to korangar/archive/data/texture/collapsed_arrow.png diff --git a/korangar/data/texture/diagonal.png b/korangar/archive/data/texture/diagonal.png similarity index 100% rename from korangar/data/texture/diagonal.png rename to korangar/archive/data/texture/diagonal.png diff --git a/korangar/data/texture/effect.png b/korangar/archive/data/texture/effect.png similarity index 100% rename from korangar/data/texture/effect.png rename to korangar/archive/data/texture/effect.png diff --git a/korangar/data/texture/entity.png b/korangar/archive/data/texture/entity.png similarity index 100% rename from korangar/data/texture/entity.png rename to korangar/archive/data/texture/entity.png diff --git a/korangar/data/texture/expanded_arrow.png b/korangar/archive/data/texture/expanded_arrow.png similarity index 100% rename from korangar/data/texture/expanded_arrow.png rename to korangar/archive/data/texture/expanded_arrow.png diff --git a/korangar/data/texture/font.png b/korangar/archive/data/texture/font.png similarity index 100% rename from korangar/data/texture/font.png rename to korangar/archive/data/texture/font.png diff --git a/korangar/data/texture/goal.png b/korangar/archive/data/texture/goal.png similarity index 100% rename from korangar/data/texture/goal.png rename to korangar/archive/data/texture/goal.png diff --git a/korangar/data/texture/light.png b/korangar/archive/data/texture/light.png similarity index 100% rename from korangar/data/texture/light.png rename to korangar/archive/data/texture/light.png diff --git a/korangar/data/texture/missing.bmp b/korangar/archive/data/texture/missing.bmp similarity index 100% rename from korangar/data/texture/missing.bmp rename to korangar/archive/data/texture/missing.bmp diff --git a/korangar/data/texture/missing.png b/korangar/archive/data/texture/missing.png similarity index 100% rename from korangar/data/texture/missing.png rename to korangar/archive/data/texture/missing.png diff --git a/korangar/data/texture/missing.tga b/korangar/archive/data/texture/missing.tga similarity index 100% rename from korangar/data/texture/missing.tga rename to korangar/archive/data/texture/missing.tga diff --git a/korangar/data/texture/model/missing.bmp b/korangar/archive/data/texture/model/missing.bmp similarity index 100% rename from korangar/data/texture/model/missing.bmp rename to korangar/archive/data/texture/model/missing.bmp diff --git a/korangar/data/texture/object.png b/korangar/archive/data/texture/object.png similarity index 100% rename from korangar/data/texture/object.png rename to korangar/archive/data/texture/object.png diff --git a/korangar/data/texture/particle.png b/korangar/archive/data/texture/particle.png similarity index 100% rename from korangar/data/texture/particle.png rename to korangar/archive/data/texture/particle.png diff --git a/korangar/data/texture/sound.png b/korangar/archive/data/texture/sound.png similarity index 100% rename from korangar/data/texture/sound.png rename to korangar/archive/data/texture/sound.png diff --git a/korangar/data/texture/straight.png b/korangar/archive/data/texture/straight.png similarity index 100% rename from korangar/data/texture/straight.png rename to korangar/archive/data/texture/straight.png diff --git a/korangar/data/texture/unchecked_box.png b/korangar/archive/data/texture/unchecked_box.png similarity index 100% rename from korangar/data/texture/unchecked_box.png rename to korangar/archive/data/texture/unchecked_box.png diff --git a/src/debug/logging/colors.rs b/korangar/src/debug/logging/colors.rs similarity index 100% rename from src/debug/logging/colors.rs rename to korangar/src/debug/logging/colors.rs diff --git a/src/debug/logging/mod.rs b/korangar/src/debug/logging/mod.rs similarity index 100% rename from src/debug/logging/mod.rs rename to korangar/src/debug/logging/mod.rs diff --git a/src/debug/logging/print.rs b/korangar/src/debug/logging/print.rs similarity index 100% rename from src/debug/logging/print.rs rename to korangar/src/debug/logging/print.rs diff --git a/src/debug/logging/stack.rs b/korangar/src/debug/logging/stack.rs similarity index 100% rename from src/debug/logging/stack.rs rename to korangar/src/debug/logging/stack.rs diff --git a/src/debug/logging/symbols.rs b/korangar/src/debug/logging/symbols.rs similarity index 100% rename from src/debug/logging/symbols.rs rename to korangar/src/debug/logging/symbols.rs diff --git a/src/debug/logging/timer.rs b/korangar/src/debug/logging/timer.rs similarity index 100% rename from src/debug/logging/timer.rs rename to korangar/src/debug/logging/timer.rs diff --git a/src/debug/mod.rs b/korangar/src/debug/mod.rs similarity index 90% rename from src/debug/mod.rs rename to korangar/src/debug/mod.rs index cd728988..b5ee2e6c 100644 --- a/src/debug/mod.rs +++ b/korangar/src/debug/mod.rs @@ -9,7 +9,7 @@ pub use self::profiling::*; #[cfg(test)] mod debug_condition { - use procedural::debug_condition; + use korangar_procedural::debug_condition; #[test] #[should_panic] diff --git a/src/debug/profiling/measurement.rs b/korangar/src/debug/profiling/measurement.rs similarity index 100% rename from src/debug/profiling/measurement.rs rename to korangar/src/debug/profiling/measurement.rs diff --git a/src/debug/profiling/mod.rs b/korangar/src/debug/profiling/mod.rs similarity index 94% rename from src/debug/profiling/mod.rs rename to korangar/src/debug/profiling/mod.rs index 25a05365..01dd3455 100644 --- a/src/debug/profiling/mod.rs +++ b/korangar/src/debug/profiling/mod.rs @@ -3,7 +3,7 @@ mod ring_buffer; mod statistics; use std::mem::MaybeUninit; -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::{LazyLock, Mutex, MutexGuard}; use std::time::Instant; @@ -22,15 +22,23 @@ static mut SHADOW_THREAD_PROFILER: LazyLock> = LazyLock::new(|| static mut DEFERRED_THREAD_PROFILER: LazyLock> = LazyLock::new(|| Mutex::new(Profiler::default())); static mut PROFILER_HALTED: AtomicBool = AtomicBool::new(false); +static mut PROFILER_HALTED_VERSION: AtomicUsize = AtomicUsize::new(0); pub fn set_profiler_halted(running: bool) { - unsafe { PROFILER_HALTED.store(running, std::sync::atomic::Ordering::Relaxed) }; + unsafe { + PROFILER_HALTED.store(running, std::sync::atomic::Ordering::Relaxed); + PROFILER_HALTED_VERSION.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + }; } pub fn is_profiler_halted() -> bool { unsafe { PROFILER_HALTED.load(std::sync::atomic::Ordering::Relaxed) } } +pub fn get_profiler_halted_version() -> usize { + unsafe { PROFILER_HALTED_VERSION.load(std::sync::atomic::Ordering::Relaxed) } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProfilerThread { Main, diff --git a/src/debug/profiling/ring_buffer.rs b/korangar/src/debug/profiling/ring_buffer.rs similarity index 100% rename from src/debug/profiling/ring_buffer.rs rename to korangar/src/debug/profiling/ring_buffer.rs diff --git a/src/debug/profiling/statistics.rs b/korangar/src/debug/profiling/statistics.rs similarity index 100% rename from src/debug/profiling/statistics.rs rename to korangar/src/debug/profiling/statistics.rs diff --git a/src/graphics/cameras/debug.rs b/korangar/src/graphics/cameras/debug.rs similarity index 99% rename from src/graphics/cameras/debug.rs rename to korangar/src/graphics/cameras/debug.rs index c7d56056..adaeb536 100644 --- a/src/graphics/cameras/debug.rs +++ b/korangar/src/graphics/cameras/debug.rs @@ -4,7 +4,7 @@ use cgmath::{Array, EuclideanSpace, InnerSpace, Matrix4, MetricSpace, Point3, Ra use super::Camera; use crate::graphics::Transform; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; const LOOK_AROUND_SPEED: f32 = 0.005; const FLY_SPEED_FAST: f32 = 1000.0; diff --git a/src/graphics/cameras/mod.rs b/korangar/src/graphics/cameras/mod.rs similarity index 96% rename from src/graphics/cameras/mod.rs rename to korangar/src/graphics/cameras/mod.rs index 8732c41a..25833b4c 100644 --- a/src/graphics/cameras/mod.rs +++ b/korangar/src/graphics/cameras/mod.rs @@ -12,7 +12,7 @@ pub use self::player::PlayerCamera; pub use self::shadow::ShadowCamera; pub use self::start::StartCamera; use crate::graphics::{SmoothedValue, Transform}; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; fn direction(vector: Vector2) -> usize { let inverted = false; diff --git a/src/graphics/cameras/player.rs b/korangar/src/graphics/cameras/player.rs similarity index 99% rename from src/graphics/cameras/player.rs rename to korangar/src/graphics/cameras/player.rs index 4b28eb7b..d3ac26c8 100644 --- a/src/graphics/cameras/player.rs +++ b/korangar/src/graphics/cameras/player.rs @@ -4,7 +4,7 @@ use cgmath::{Array, EuclideanSpace, InnerSpace, Matrix4, MetricSpace, Point3, Ra use super::{Camera, SmoothedValue}; use crate::graphics::Transform; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; const ZOOM_SPEED: f32 = 2.0; const ROTATION_SPEED: f32 = 0.02; diff --git a/src/graphics/cameras/shadow.rs b/korangar/src/graphics/cameras/shadow.rs similarity index 99% rename from src/graphics/cameras/shadow.rs rename to korangar/src/graphics/cameras/shadow.rs index 6cdcde1e..1a31e5a4 100644 --- a/src/graphics/cameras/shadow.rs +++ b/korangar/src/graphics/cameras/shadow.rs @@ -2,7 +2,7 @@ use cgmath::{Array, EuclideanSpace, InnerSpace, Matrix4, MetricSpace, Point3, Sq use super::Camera; use crate::graphics::Transform; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; pub struct ShadowCamera { focus_point: Point3, diff --git a/src/graphics/cameras/start.rs b/korangar/src/graphics/cameras/start.rs similarity index 99% rename from src/graphics/cameras/start.rs rename to korangar/src/graphics/cameras/start.rs index 427be54a..a30df064 100644 --- a/src/graphics/cameras/start.rs +++ b/korangar/src/graphics/cameras/start.rs @@ -4,7 +4,7 @@ use cgmath::{Array, EuclideanSpace, InnerSpace, Matrix4, MetricSpace, Point3, Ra use super::Camera; use crate::graphics::Transform; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; const DEFAULT_ZOOM: f32 = 150.0; const ROTATION_SPEED: f32 = 0.03; diff --git a/src/graphics/color.rs b/korangar/src/graphics/color.rs similarity index 98% rename from src/graphics/color.rs rename to korangar/src/graphics/color.rs index be715855..7452b512 100644 --- a/src/graphics/color.rs +++ b/korangar/src/graphics/color.rs @@ -1,5 +1,5 @@ -use procedural::PrototypeElement; -use ragnarok_procedural::ByteConvertable; +use korangar_procedural::PrototypeElement; +use ragnarok_bytes::ByteConvertable; use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)] diff --git a/src/graphics/memory.rs b/korangar/src/graphics/memory.rs similarity index 99% rename from src/graphics/memory.rs rename to korangar/src/graphics/memory.rs index 7d21bd86..6d0491d9 100644 --- a/src/graphics/memory.rs +++ b/korangar/src/graphics/memory.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use std::sync::Arc; use derive_new::new; -use procedural::profile; +use korangar_procedural::profile; use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}; use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}; use vulkano::command_buffer::allocator::{CommandBufferAllocator, StandardCommandBufferAllocator}; diff --git a/src/graphics/mod.rs b/korangar/src/graphics/mod.rs similarity index 100% rename from src/graphics/mod.rs rename to korangar/src/graphics/mod.rs diff --git a/src/graphics/particles/mod.rs b/korangar/src/graphics/particles/mod.rs similarity index 98% rename from src/graphics/particles/mod.rs rename to korangar/src/graphics/particles/mod.rs index 63e34adc..b6bf44ae 100644 --- a/src/graphics/particles/mod.rs +++ b/korangar/src/graphics/particles/mod.rs @@ -2,11 +2,11 @@ use std::collections::HashMap; use cgmath::{Vector2, Vector3}; use derive_new::new; -use procedural::profile; +use korangar_procedural::profile; use rand::{thread_rng, Rng}; use crate::graphics::*; -use crate::interface::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::loaders::{GameFileLoader, TextureLoader}; use crate::network::{EntityId, QuestColor, QuestEffectPacket}; use crate::world::*; diff --git a/src/graphics/renderers/deferred/ambient/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/ambient/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/ambient/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/ambient/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/ambient/mod.rs b/korangar/src/graphics/renderers/deferred/ambient/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/ambient/mod.rs rename to korangar/src/graphics/renderers/deferred/ambient/mod.rs index 5b379d82..e3d7c0f7 100644 --- a/src/graphics/renderers/deferred/ambient/mod.rs +++ b/korangar/src/graphics/renderers/deferred/ambient/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/deferred/ambient/fragment_shader.glsl") use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::viewport::Viewport; diff --git a/src/graphics/renderers/deferred/ambient/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/ambient/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/ambient/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/ambient/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/box/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/box/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/box/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/box/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/box/mod.rs b/korangar/src/graphics/renderers/deferred/box/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/box/mod.rs rename to korangar/src/graphics/renderers/deferred/box/mod.rs index 6ad6a6de..b92655df 100644 --- a/src/graphics/renderers/deferred/box/mod.rs +++ b/korangar/src/graphics/renderers/deferred/box/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/box/fragment_shader.glsl"); use std::sync::Arc; use cgmath::Vector3; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::input_assembly::PrimitiveTopology; diff --git a/src/graphics/renderers/deferred/box/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/box/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/box/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/box/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/buffer/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/buffer/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/buffer/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/buffer/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/buffer/mod.rs b/korangar/src/graphics/renderers/deferred/buffer/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/buffer/mod.rs rename to korangar/src/graphics/renderers/deferred/buffer/mod.rs index c6c13363..eab20f7e 100644 --- a/src/graphics/renderers/deferred/buffer/mod.rs +++ b/korangar/src/graphics/renderers/deferred/buffer/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/deferred/buffer/fragment_shader.glsl"); use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/deferred/buffer/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/buffer/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/buffer/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/buffer/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/directional/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/directional/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/directional/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/directional/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/directional/mod.rs b/korangar/src/graphics/renderers/deferred/directional/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/directional/mod.rs rename to korangar/src/graphics/renderers/deferred/directional/mod.rs index 9a390448..160847f7 100644 --- a/src/graphics/renderers/deferred/directional/mod.rs +++ b/korangar/src/graphics/renderers/deferred/directional/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/directional/fragment_shader.gl use std::sync::Arc; use cgmath::{Matrix4, Vector3}; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/deferred/directional/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/directional/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/directional/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/directional/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/effect/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/effect/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/effect/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/effect/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/effect/mod.rs b/korangar/src/graphics/renderers/deferred/effect/mod.rs similarity index 94% rename from src/graphics/renderers/deferred/effect/mod.rs rename to korangar/src/graphics/renderers/deferred/effect/mod.rs index 03c36eba..d935d62b 100644 --- a/src/graphics/renderers/deferred/effect/mod.rs +++ b/korangar/src/graphics/renderers/deferred/effect/mod.rs @@ -4,21 +4,23 @@ fragment_shader!("src/graphics/renderers/deferred/effect/fragment_shader.glsl"); use std::sync::Arc; use cgmath::{Matrix2, Vector2}; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; +use vulkano::image::view::ImageView; use vulkano::pipeline::graphics::viewport::Viewport; use vulkano::pipeline::{GraphicsPipeline, PipelineBindPoint}; use vulkano::render_pass::Subpass; use vulkano::shader::EntryPoint; use self::vertex_shader::Constants; -use super::DeferredSubrenderer; +use super::{DeferredRenderer, DeferredSubrenderer}; +use crate::graphics::memory::allocate_descriptor_set; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::renderers::sampler::{create_new_sampler, SamplerType}; -use crate::graphics::*; -use crate::interface::ScreenSize; +use crate::graphics::{Color, MemoryAllocator, Renderer, EFFECT_ATTACHMENT_BLEND}; +use crate::interface::layout::ScreenSize; pub struct EffectRenderer { memory_allocator: Arc, diff --git a/src/graphics/renderers/deferred/effect/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/effect/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/effect/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/effect/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/entity/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/entity/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/entity/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/entity/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/entity/mod.rs b/korangar/src/graphics/renderers/deferred/entity/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/entity/mod.rs rename to korangar/src/graphics/renderers/deferred/entity/mod.rs index 06f0606a..e5757875 100644 --- a/src/graphics/renderers/deferred/entity/mod.rs +++ b/korangar/src/graphics/renderers/deferred/entity/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/entity/fragment_shader.glsl"); use std::sync::Arc; use cgmath::{Vector2, Vector3}; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/deferred/entity/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/entity/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/entity/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/entity/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/geometry/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/geometry/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/geometry/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/geometry/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/geometry/mod.rs b/korangar/src/graphics/renderers/deferred/geometry/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/geometry/mod.rs rename to korangar/src/graphics/renderers/deferred/geometry/mod.rs index c1856a86..56cc4383 100644 --- a/src/graphics/renderers/deferred/geometry/mod.rs +++ b/korangar/src/graphics/renderers/deferred/geometry/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/geometry/fragment_shader.glsl" use std::sync::Arc; use cgmath::Matrix4; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/deferred/geometry/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/geometry/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/geometry/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/geometry/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/indicator/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/indicator/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/indicator/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/indicator/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/indicator/mod.rs b/korangar/src/graphics/renderers/deferred/indicator/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/indicator/mod.rs rename to korangar/src/graphics/renderers/deferred/indicator/mod.rs index a00b526f..7f63341e 100644 --- a/src/graphics/renderers/deferred/indicator/mod.rs +++ b/korangar/src/graphics/renderers/deferred/indicator/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/indicator/fragment_shader.glsl use std::sync::Arc; use cgmath::Vector3; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/deferred/indicator/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/indicator/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/indicator/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/indicator/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/mod.rs b/korangar/src/graphics/renderers/deferred/mod.rs similarity index 98% rename from src/graphics/renderers/deferred/mod.rs rename to korangar/src/graphics/renderers/deferred/mod.rs index 85f3c6b9..f4892ae7 100644 --- a/src/graphics/renderers/deferred/mod.rs +++ b/korangar/src/graphics/renderers/deferred/mod.rs @@ -20,7 +20,8 @@ use std::sync::Arc; #[cfg(feature = "debug")] use cgmath::SquareMatrix; use cgmath::{Matrix4, Vector2, Vector3}; -use procedural::profile; +use korangar_interface::application::FontSizeTrait; +use korangar_procedural::profile; use vulkano::device::{DeviceOwned, Queue}; use vulkano::format::Format; use vulkano::image::Image; @@ -49,8 +50,8 @@ use crate::graphics::{ EntityRenderer as EntityRendererTrait, GeometryRenderer as GeometryRendererTrait, IndicatorRenderer as IndicatorRendererTrait, SpriteRenderer as SpriteRendererTrait, *, }; -use crate::interface::{ScreenClip, ScreenPosition, ScreenSize}; -use crate::loaders::{GameFileLoader, TextureLoader}; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::loaders::{FontSize, GameFileLoader, TextureLoader}; use crate::network::EntityId; #[cfg(feature = "debug")] use crate::world::{BoundingBox, MarkerIdentifier}; @@ -387,7 +388,7 @@ impl DeferredRenderer { text: &str, mut position: ScreenPosition, color: Color, - font_size: f32, + font_size: FontSize, ) { let window_size = self.get_window_size(); @@ -398,13 +399,13 @@ impl DeferredRenderer { self.font_map.clone(), window_size, position, - ScreenSize::uniform(font_size), + ScreenSize::uniform(font_size.get_value()), color, 10, index, true, ); - position.left += font_size / 2.0; + position.left += font_size.get_value() / 2.0; } } diff --git a/src/graphics/renderers/deferred/overlay/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/overlay/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/overlay/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/overlay/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/overlay/mod.rs b/korangar/src/graphics/renderers/deferred/overlay/mod.rs similarity index 98% rename from src/graphics/renderers/deferred/overlay/mod.rs rename to korangar/src/graphics/renderers/deferred/overlay/mod.rs index 4cf75b43..393a8684 100644 --- a/src/graphics/renderers/deferred/overlay/mod.rs +++ b/korangar/src/graphics/renderers/deferred/overlay/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/deferred/overlay/fragment_shader.glsl") use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::viewport::Viewport; diff --git a/src/graphics/renderers/deferred/overlay/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/overlay/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/overlay/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/overlay/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/point/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/point/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/point/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/point/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/point/mod.rs b/korangar/src/graphics/renderers/deferred/point/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/point/mod.rs rename to korangar/src/graphics/renderers/deferred/point/mod.rs index 24d78975..658126e7 100644 --- a/src/graphics/renderers/deferred/point/mod.rs +++ b/korangar/src/graphics/renderers/deferred/point/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/point/fragment_shader.glsl"); use std::sync::Arc; use cgmath::Vector3; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::padded::Padded; diff --git a/src/graphics/renderers/deferred/point/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/point/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/point/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/point/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/rectangle/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/rectangle/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/rectangle/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/rectangle/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/rectangle/mod.rs b/korangar/src/graphics/renderers/deferred/rectangle/mod.rs similarity index 97% rename from src/graphics/renderers/deferred/rectangle/mod.rs rename to korangar/src/graphics/renderers/deferred/rectangle/mod.rs index 55f0ecd5..2c7cbe4e 100644 --- a/src/graphics/renderers/deferred/rectangle/mod.rs +++ b/korangar/src/graphics/renderers/deferred/rectangle/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/deferred/rectangle/fragment_shader.glsl use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::viewport::Viewport; use vulkano::pipeline::{GraphicsPipeline, Pipeline}; @@ -14,7 +14,7 @@ use self::vertex_shader::Constants; use super::DeferredSubrenderer; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::*; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; pub struct RectangleRenderer { vertex_shader: EntryPoint, diff --git a/src/graphics/renderers/deferred/rectangle/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/rectangle/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/rectangle/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/rectangle/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/sprite/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/sprite/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/sprite/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/sprite/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/sprite/mod.rs b/korangar/src/graphics/renderers/deferred/sprite/mod.rs similarity index 98% rename from src/graphics/renderers/deferred/sprite/mod.rs rename to korangar/src/graphics/renderers/deferred/sprite/mod.rs index 96b3edee..c5251c48 100644 --- a/src/graphics/renderers/deferred/sprite/mod.rs +++ b/korangar/src/graphics/renderers/deferred/sprite/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/deferred/sprite/fragment_shader.glsl"); use std::sync::Arc; use cgmath::Vector2; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; @@ -18,7 +18,7 @@ use super::DeferredSubrenderer; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::renderers::sampler::{create_new_sampler, SamplerType}; use crate::graphics::*; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; #[cfg(feature = "debug")] use crate::loaders::{GameFileLoader, TextureLoader}; #[cfg(feature = "debug")] diff --git a/src/graphics/renderers/deferred/sprite/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/sprite/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/sprite/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/sprite/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/water/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/water/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/water/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/water/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/water/mod.rs b/korangar/src/graphics/renderers/deferred/water/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/water/mod.rs rename to korangar/src/graphics/renderers/deferred/water/mod.rs index 9be839b2..751c801f 100644 --- a/src/graphics/renderers/deferred/water/mod.rs +++ b/korangar/src/graphics/renderers/deferred/water/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/deferred/water/fragment_shader.glsl"); use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::SampleCount; diff --git a/src/graphics/renderers/deferred/water/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/water/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/water/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/water/vertex_shader.glsl diff --git a/src/graphics/renderers/deferred/water_light/fragment_shader.glsl b/korangar/src/graphics/renderers/deferred/water_light/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/water_light/fragment_shader.glsl rename to korangar/src/graphics/renderers/deferred/water_light/fragment_shader.glsl diff --git a/src/graphics/renderers/deferred/water_light/mod.rs b/korangar/src/graphics/renderers/deferred/water_light/mod.rs similarity index 99% rename from src/graphics/renderers/deferred/water_light/mod.rs rename to korangar/src/graphics/renderers/deferred/water_light/mod.rs index 6be9aace..2a86609b 100644 --- a/src/graphics/renderers/deferred/water_light/mod.rs +++ b/korangar/src/graphics/renderers/deferred/water_light/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/deferred/water_light/fragment_shader.gl use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::viewport::Viewport; diff --git a/src/graphics/renderers/deferred/water_light/vertex_shader.glsl b/korangar/src/graphics/renderers/deferred/water_light/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/deferred/water_light/vertex_shader.glsl rename to korangar/src/graphics/renderers/deferred/water_light/vertex_shader.glsl diff --git a/src/graphics/renderers/image.rs b/korangar/src/graphics/renderers/image.rs similarity index 100% rename from src/graphics/renderers/image.rs rename to korangar/src/graphics/renderers/image.rs diff --git a/src/graphics/renderers/interface/mod.rs b/korangar/src/graphics/renderers/interface/mod.rs similarity index 76% rename from src/graphics/renderers/interface/mod.rs rename to korangar/src/graphics/renderers/interface/mod.rs index 22b3419d..727d2571 100644 --- a/src/graphics/renderers/interface/mod.rs +++ b/korangar/src/graphics/renderers/interface/mod.rs @@ -6,8 +6,8 @@ use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; -use cgmath::Vector2; -use procedural::profile; +use korangar_interface::application::Application; +use korangar_procedural::profile; use vulkano::device::{DeviceOwned, Queue}; use vulkano::format::{ClearColorValue, Format}; use vulkano::image::view::ImageView; @@ -20,7 +20,8 @@ use self::sprite::SpriteRenderer; use self::text::TextRenderer; use super::{IntoFormat, SubpassAttachments}; use crate::graphics::{Color, MemoryAllocator, Renderer, SingleRenderTarget, SpriteRenderer as SpriteRendererTrait}; -use crate::interface::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::loaders::{FontLoader, GameFileLoader, TextureLoader}; #[derive(PartialEq, Eq)] @@ -103,10 +104,6 @@ impl InterfaceRenderer { } } - pub fn get_text_dimensions(&self, text: &str, font_size: f32, available_width: f32) -> Vector2 { - self.font_loader.borrow().get_text_dimensions(text, font_size, available_width) - } - #[profile("re-create interface pipeline")] pub fn recreate_pipeline(&mut self, viewport: Viewport, dimensions: [u32; 2]) { let device = self.memory_allocator.device().clone(); @@ -139,34 +136,60 @@ impl InterfaceRenderer { height: self.dimensions[1] as f32, } } +} + +impl korangar_interface::application::InterfaceRenderer for InterfaceRenderer { + type Target = ::Target; - pub fn render_rectangle( + fn get_text_dimensions( &self, - render_target: &mut ::Target, - position: ScreenPosition, - size: ScreenSize, - screen_clip: ScreenClip, - corner_radius: CornerRadius, - color: Color, + text: &str, + font_size: ::FontSize, + available_width: f32, + ) -> ::Size { + self.font_loader.borrow().get_text_dimensions(text, font_size, available_width) + } + + fn render_rectangle( + &self, + render_target: &mut Self::Target, + position: ::Position, + size: ::Size, + clip: ::Clip, + corner_radius: ::CornerRadius, + color: ::Color, ) { self.rectangle_renderer.render( render_target, self.get_window_size(), position, size, - screen_clip, + clip, corner_radius, color, ); } - pub fn render_checkbox( + fn render_text( &self, - render_target: &mut ::Target, - position: ScreenPosition, - size: ScreenSize, - screen_clip: ScreenClip, - color: Color, + render_target: &mut Self::Target, + text: &str, + position: ::Position, + clip: ::Clip, + color: ::Color, + font_size: ::FontSize, + ) -> f32 { + self.text_renderer + .render(render_target, text, self.get_window_size(), position, clip, color, font_size) + } + + fn render_checkbox( + &self, + render_target: &mut Self::Target, + position: ::Position, + size: ::Size, + clip: ::Clip, + color: ::Color, checked: bool, ) { let texture = match checked { @@ -174,16 +197,16 @@ impl InterfaceRenderer { false => self.unchecked_box_texture.clone(), }; - self.render_sprite(render_target, texture, position, size, screen_clip, color, true); + self.render_sprite(render_target, texture, position, size, clip, color, true); } - pub fn render_expand_arrow( + fn render_expand_arrow( &self, - render_target: &mut ::Target, - position: ScreenPosition, - size: ScreenSize, - screen_clip: ScreenClip, - color: Color, + render_target: &mut Self::Target, + position: ::Position, + size: ::Size, + clip: ::Clip, + color: ::Color, expanded: bool, ) { let texture = match expanded { @@ -191,27 +214,7 @@ impl InterfaceRenderer { false => self.collapsed_arrow_texture.clone(), }; - self.render_sprite(render_target, texture, position, size, screen_clip, color, true); - } - - pub fn render_text( - &self, - render_target: &mut ::Target, - text: &str, - position: ScreenPosition, - screen_clip: ScreenClip, - color: Color, - font_size: f32, - ) -> f32 { - self.text_renderer.render( - render_target, - text, - self.get_window_size(), - position, - screen_clip, - color, - font_size, - ) + self.render_sprite(render_target, texture, position, size, clip, color, true); } } diff --git a/src/graphics/renderers/interface/rectangle/fragment_shader.glsl b/korangar/src/graphics/renderers/interface/rectangle/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/interface/rectangle/fragment_shader.glsl rename to korangar/src/graphics/renderers/interface/rectangle/fragment_shader.glsl diff --git a/src/graphics/renderers/interface/rectangle/mod.rs b/korangar/src/graphics/renderers/interface/rectangle/mod.rs similarity index 96% rename from src/graphics/renderers/interface/rectangle/mod.rs rename to korangar/src/graphics/renderers/interface/rectangle/mod.rs index 4d7cf9ca..621502e0 100644 --- a/src/graphics/renderers/interface/rectangle/mod.rs +++ b/korangar/src/graphics/renderers/interface/rectangle/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/interface/rectangle/fragment_shader.gls use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::SampleCount; use vulkano::pipeline::graphics::viewport::Viewport; @@ -15,7 +15,7 @@ use self::vertex_shader::Constants; use super::InterfaceSubrenderer; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::*; -use crate::interface::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; pub struct RectangleRenderer { vertex_shader: EntryPoint, diff --git a/src/graphics/renderers/interface/rectangle/vertex_shader.glsl b/korangar/src/graphics/renderers/interface/rectangle/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/interface/rectangle/vertex_shader.glsl rename to korangar/src/graphics/renderers/interface/rectangle/vertex_shader.glsl diff --git a/src/graphics/renderers/interface/sprite/fragment_shader.glsl b/korangar/src/graphics/renderers/interface/sprite/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/interface/sprite/fragment_shader.glsl rename to korangar/src/graphics/renderers/interface/sprite/fragment_shader.glsl diff --git a/src/graphics/renderers/interface/sprite/mod.rs b/korangar/src/graphics/renderers/interface/sprite/mod.rs similarity index 97% rename from src/graphics/renderers/interface/sprite/mod.rs rename to korangar/src/graphics/renderers/interface/sprite/mod.rs index f9bec5e1..8c198983 100644 --- a/src/graphics/renderers/interface/sprite/mod.rs +++ b/korangar/src/graphics/renderers/interface/sprite/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/interface/sprite/fragment_shader.glsl") use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; @@ -18,7 +18,7 @@ use super::InterfaceSubrenderer; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::renderers::sampler::{create_new_sampler, SamplerType}; use crate::graphics::*; -use crate::interface::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; pub struct SpriteRenderer { memory_allocator: Arc, diff --git a/src/graphics/renderers/interface/sprite/vertex_shader.glsl b/korangar/src/graphics/renderers/interface/sprite/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/interface/sprite/vertex_shader.glsl rename to korangar/src/graphics/renderers/interface/sprite/vertex_shader.glsl diff --git a/src/graphics/renderers/interface/text/fragment_shader.glsl b/korangar/src/graphics/renderers/interface/text/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/interface/text/fragment_shader.glsl rename to korangar/src/graphics/renderers/interface/text/fragment_shader.glsl diff --git a/src/graphics/renderers/interface/text/mod.rs b/korangar/src/graphics/renderers/interface/text/mod.rs similarity index 96% rename from src/graphics/renderers/interface/text/mod.rs rename to korangar/src/graphics/renderers/interface/text/mod.rs index ceea9846..54d281ab 100644 --- a/src/graphics/renderers/interface/text/mod.rs +++ b/korangar/src/graphics/renderers/interface/text/mod.rs @@ -5,7 +5,7 @@ use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; @@ -20,8 +20,8 @@ use super::InterfaceSubrenderer; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::renderers::sampler::{create_new_sampler, SamplerType}; use crate::graphics::*; -use crate::interface::{ScreenClip, ScreenPosition, ScreenSize}; -use crate::loaders::FontLoader; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::loaders::{FontLoader, FontSize}; pub struct TextRenderer { memory_allocator: Arc, @@ -87,7 +87,7 @@ impl TextRenderer { screen_position: ScreenPosition, screen_clip: ScreenClip, color: Color, - font_size: f32, + font_size: FontSize, ) -> f32 { if render_target.bind_subrenderer(InterfaceSubrenderer::Text) { self.bind_pipeline(render_target); diff --git a/src/graphics/renderers/interface/text/vertex_shader.glsl b/korangar/src/graphics/renderers/interface/text/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/interface/text/vertex_shader.glsl rename to korangar/src/graphics/renderers/interface/text/vertex_shader.glsl diff --git a/src/graphics/renderers/mod.rs b/korangar/src/graphics/renderers/mod.rs similarity index 99% rename from src/graphics/renderers/mod.rs rename to korangar/src/graphics/renderers/mod.rs index 721bc7ec..39a3ada6 100644 --- a/src/graphics/renderers/mod.rs +++ b/korangar/src/graphics/renderers/mod.rs @@ -43,8 +43,8 @@ use std::marker::{ConstParamTy, PhantomData}; use std::sync::Arc; use cgmath::{Matrix4, Vector2, Vector3}; +use korangar_procedural::profile; use option_ext::OptionExt; -use procedural::profile; use vulkano::buffer::{Buffer, BufferUsage, Subbuffer}; use vulkano::command_buffer::{ AutoCommandBufferBuilder, ClearAttachment, ClearRect, CommandBufferUsage, CopyImageToBufferInfo, PrimaryAutoCommandBuffer, @@ -76,7 +76,7 @@ use super::{Color, MemoryAllocator, ModelVertex}; #[cfg(feature = "debug")] use crate::debug::*; use crate::graphics::Camera; -use crate::interface::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::network::EntityId; #[cfg(feature = "debug")] use crate::world::MarkerIdentifier; @@ -340,6 +340,7 @@ impl DeferredRenderTarget { .unwrap(); self.state = RenderTargetState::Rendering(builder); + self.bound_subrenderer = None; } pub fn bind_subrenderer(&mut self, subrenderer: DeferredSubrenderer) -> bool { @@ -405,8 +406,6 @@ impl DeferredRenderTarget { #[cfg(feature = "debug")] flush_measurement.stop(); - - self.bound_subrenderer = None; } } @@ -481,6 +480,7 @@ impl PickerRenderTarget { .unwrap(); self.state = RenderTargetState::Rendering(builder); + self.bound_subrenderer = None; } #[profile] @@ -511,7 +511,6 @@ impl PickerRenderTarget { .unwrap(); self.state = RenderTargetState::Fence(fence); - self.bound_subrenderer = None; } } @@ -602,7 +601,9 @@ impl SingleRenderTarget { builder .begin_render_pass(render_pass_begin_info, SubpassBeginInfo::default()) .unwrap(); + self.state = RenderTargetState::Rendering(builder); + self.bound_subrenderer = None; } #[profile("finalize buffer")] @@ -620,7 +621,6 @@ impl SingleRenderTarget { .unwrap(); self.state = RenderTargetState::Semaphore(semaphore); - self.bound_subrenderer = None; } } @@ -666,6 +666,7 @@ impl SingleRenderTarget { } self.state = RenderTargetState::Rendering(builder); + self.bound_subrenderer = None; } #[profile("finish buffer")] @@ -690,6 +691,5 @@ impl SingleRenderTarget { .unwrap(); self.state = RenderTargetState::Semaphore(semaphore); - self.bound_subrenderer = None; } } diff --git a/src/graphics/renderers/picker/entity/fragment_shader.glsl b/korangar/src/graphics/renderers/picker/entity/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/entity/fragment_shader.glsl rename to korangar/src/graphics/renderers/picker/entity/fragment_shader.glsl diff --git a/src/graphics/renderers/picker/entity/mod.rs b/korangar/src/graphics/renderers/picker/entity/mod.rs similarity index 99% rename from src/graphics/renderers/picker/entity/mod.rs rename to korangar/src/graphics/renderers/picker/entity/mod.rs index e7a0dfa5..bb4a41be 100644 --- a/src/graphics/renderers/picker/entity/mod.rs +++ b/korangar/src/graphics/renderers/picker/entity/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/picker/entity/fragment_shader.glsl"); use std::sync::Arc; use cgmath::{Vector2, Vector3}; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/picker/entity/vertex_shader.glsl b/korangar/src/graphics/renderers/picker/entity/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/entity/vertex_shader.glsl rename to korangar/src/graphics/renderers/picker/entity/vertex_shader.glsl diff --git a/src/graphics/renderers/picker/geometry/fragment_shader.glsl b/korangar/src/graphics/renderers/picker/geometry/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/geometry/fragment_shader.glsl rename to korangar/src/graphics/renderers/picker/geometry/fragment_shader.glsl diff --git a/src/graphics/renderers/picker/geometry/mod.rs b/korangar/src/graphics/renderers/picker/geometry/mod.rs similarity index 99% rename from src/graphics/renderers/picker/geometry/mod.rs rename to korangar/src/graphics/renderers/picker/geometry/mod.rs index 3c9fb2db..55a97924 100644 --- a/src/graphics/renderers/picker/geometry/mod.rs +++ b/korangar/src/graphics/renderers/picker/geometry/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/picker/geometry/fragment_shader.glsl"); use std::sync::Arc; use cgmath::Matrix4; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/picker/geometry/vertex_shader.glsl b/korangar/src/graphics/renderers/picker/geometry/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/geometry/vertex_shader.glsl rename to korangar/src/graphics/renderers/picker/geometry/vertex_shader.glsl diff --git a/src/graphics/renderers/picker/marker/fragment_shader.glsl b/korangar/src/graphics/renderers/picker/marker/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/marker/fragment_shader.glsl rename to korangar/src/graphics/renderers/picker/marker/fragment_shader.glsl diff --git a/src/graphics/renderers/picker/marker/mod.rs b/korangar/src/graphics/renderers/picker/marker/mod.rs similarity index 97% rename from src/graphics/renderers/picker/marker/mod.rs rename to korangar/src/graphics/renderers/picker/marker/mod.rs index 8f4d85ec..0ad9c803 100644 --- a/src/graphics/renderers/picker/marker/mod.rs +++ b/korangar/src/graphics/renderers/picker/marker/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/picker/marker/fragment_shader.glsl"); use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::viewport::Viewport; use vulkano::pipeline::{GraphicsPipeline, Pipeline}; @@ -14,7 +14,7 @@ use self::vertex_shader::Constants; use super::PickerSubrenderer; use crate::graphics::renderers::pipeline::PipelineBuilder; use crate::graphics::*; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; use crate::world::MarkerIdentifier; pub struct MarkerRenderer { diff --git a/src/graphics/renderers/picker/marker/vertex_shader.glsl b/korangar/src/graphics/renderers/picker/marker/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/marker/vertex_shader.glsl rename to korangar/src/graphics/renderers/picker/marker/vertex_shader.glsl diff --git a/src/graphics/renderers/picker/mod.rs b/korangar/src/graphics/renderers/picker/mod.rs similarity index 99% rename from src/graphics/renderers/picker/mod.rs rename to korangar/src/graphics/renderers/picker/mod.rs index 73f28db8..580808cb 100644 --- a/src/graphics/renderers/picker/mod.rs +++ b/korangar/src/graphics/renderers/picker/mod.rs @@ -8,7 +8,7 @@ mod tile; use std::sync::Arc; use cgmath::{Matrix4, Vector2, Vector3}; -use procedural::profile; +use korangar_procedural::profile; use vulkano::device::{DeviceOwned, Queue}; use vulkano::format::Format; use vulkano::pipeline::graphics::viewport::Viewport; diff --git a/src/graphics/renderers/picker/target.rs b/korangar/src/graphics/renderers/picker/target.rs similarity index 100% rename from src/graphics/renderers/picker/target.rs rename to korangar/src/graphics/renderers/picker/target.rs diff --git a/src/graphics/renderers/picker/tile/fragment_shader.glsl b/korangar/src/graphics/renderers/picker/tile/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/tile/fragment_shader.glsl rename to korangar/src/graphics/renderers/picker/tile/fragment_shader.glsl diff --git a/src/graphics/renderers/picker/tile/mod.rs b/korangar/src/graphics/renderers/picker/tile/mod.rs similarity index 99% rename from src/graphics/renderers/picker/tile/mod.rs rename to korangar/src/graphics/renderers/picker/tile/mod.rs index 570c11ed..8bc8fa00 100644 --- a/src/graphics/renderers/picker/tile/mod.rs +++ b/korangar/src/graphics/renderers/picker/tile/mod.rs @@ -3,7 +3,7 @@ fragment_shader!("src/graphics/renderers/picker/tile/fragment_shader.glsl"); use std::sync::Arc; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::pipeline::graphics::viewport::Viewport; diff --git a/src/graphics/renderers/picker/tile/vertex_shader.glsl b/korangar/src/graphics/renderers/picker/tile/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/picker/tile/vertex_shader.glsl rename to korangar/src/graphics/renderers/picker/tile/vertex_shader.glsl diff --git a/src/graphics/renderers/pipeline.rs b/korangar/src/graphics/renderers/pipeline.rs similarity index 100% rename from src/graphics/renderers/pipeline.rs rename to korangar/src/graphics/renderers/pipeline.rs diff --git a/src/graphics/renderers/sampler.rs b/korangar/src/graphics/renderers/sampler.rs similarity index 100% rename from src/graphics/renderers/sampler.rs rename to korangar/src/graphics/renderers/sampler.rs diff --git a/src/graphics/renderers/settings.rs b/korangar/src/graphics/renderers/settings.rs similarity index 98% rename from src/graphics/renderers/settings.rs rename to korangar/src/graphics/renderers/settings.rs index 7ddd43b1..dc6d1aae 100644 --- a/src/graphics/renderers/settings.rs +++ b/korangar/src/graphics/renderers/settings.rs @@ -1,5 +1,5 @@ use derive_new::new; -use procedural::toggle; +use korangar_procedural::toggle; #[derive(toggle, new)] pub struct RenderSettings { diff --git a/src/graphics/renderers/shadow/entity/fragment_shader.glsl b/korangar/src/graphics/renderers/shadow/entity/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/shadow/entity/fragment_shader.glsl rename to korangar/src/graphics/renderers/shadow/entity/fragment_shader.glsl diff --git a/src/graphics/renderers/shadow/entity/mod.rs b/korangar/src/graphics/renderers/shadow/entity/mod.rs similarity index 99% rename from src/graphics/renderers/shadow/entity/mod.rs rename to korangar/src/graphics/renderers/shadow/entity/mod.rs index bae7b3aa..b822363e 100644 --- a/src/graphics/renderers/shadow/entity/mod.rs +++ b/korangar/src/graphics/renderers/shadow/entity/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/shadow/entity/fragment_shader.glsl"); use std::sync::Arc; use cgmath::{Vector2, Vector3}; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/shadow/entity/vertex_shader.glsl b/korangar/src/graphics/renderers/shadow/entity/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/shadow/entity/vertex_shader.glsl rename to korangar/src/graphics/renderers/shadow/entity/vertex_shader.glsl diff --git a/src/graphics/renderers/shadow/geometry/fragment_shader.glsl b/korangar/src/graphics/renderers/shadow/geometry/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/shadow/geometry/fragment_shader.glsl rename to korangar/src/graphics/renderers/shadow/geometry/fragment_shader.glsl diff --git a/src/graphics/renderers/shadow/geometry/mod.rs b/korangar/src/graphics/renderers/shadow/geometry/mod.rs similarity index 99% rename from src/graphics/renderers/shadow/geometry/mod.rs rename to korangar/src/graphics/renderers/shadow/geometry/mod.rs index 3ec8c013..c4d43f05 100644 --- a/src/graphics/renderers/shadow/geometry/mod.rs +++ b/korangar/src/graphics/renderers/shadow/geometry/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/shadow/geometry/fragment_shader.glsl"); use std::sync::Arc; use cgmath::Matrix4; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/shadow/geometry/vertex_shader.glsl b/korangar/src/graphics/renderers/shadow/geometry/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/shadow/geometry/vertex_shader.glsl rename to korangar/src/graphics/renderers/shadow/geometry/vertex_shader.glsl diff --git a/src/graphics/renderers/shadow/indicator/fragment_shader.glsl b/korangar/src/graphics/renderers/shadow/indicator/fragment_shader.glsl similarity index 100% rename from src/graphics/renderers/shadow/indicator/fragment_shader.glsl rename to korangar/src/graphics/renderers/shadow/indicator/fragment_shader.glsl diff --git a/src/graphics/renderers/shadow/indicator/mod.rs b/korangar/src/graphics/renderers/shadow/indicator/mod.rs similarity index 99% rename from src/graphics/renderers/shadow/indicator/mod.rs rename to korangar/src/graphics/renderers/shadow/indicator/mod.rs index a6741af9..f5fd5171 100644 --- a/src/graphics/renderers/shadow/indicator/mod.rs +++ b/korangar/src/graphics/renderers/shadow/indicator/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/shadow/indicator/fragment_shader.glsl") use std::sync::Arc; use cgmath::Vector3; -use procedural::profile; +use korangar_procedural::profile; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/src/graphics/renderers/shadow/indicator/vertex_shader.glsl b/korangar/src/graphics/renderers/shadow/indicator/vertex_shader.glsl similarity index 100% rename from src/graphics/renderers/shadow/indicator/vertex_shader.glsl rename to korangar/src/graphics/renderers/shadow/indicator/vertex_shader.glsl diff --git a/src/graphics/renderers/shadow/mod.rs b/korangar/src/graphics/renderers/shadow/mod.rs similarity index 100% rename from src/graphics/renderers/shadow/mod.rs rename to korangar/src/graphics/renderers/shadow/mod.rs diff --git a/src/graphics/renderers/swapchain.rs b/korangar/src/graphics/renderers/swapchain.rs similarity index 98% rename from src/graphics/renderers/swapchain.rs rename to korangar/src/graphics/renderers/swapchain.rs index 3519fa86..a11451ca 100644 --- a/src/graphics/renderers/swapchain.rs +++ b/korangar/src/graphics/renderers/swapchain.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use cgmath::Vector2; -use procedural::profile; +use korangar_procedural::profile; use vulkano::device::physical::PhysicalDevice; use vulkano::device::{Device, Queue}; use vulkano::format::{Format, NumericFormat}; @@ -14,7 +14,7 @@ use winit::window::Window; #[cfg(feature = "debug")] use crate::debug::*; -use crate::interface::ScreenSize; +use crate::interface::layout::ScreenSize; #[derive(Debug, Clone, Copy)] pub struct PresentModeInfo { diff --git a/src/graphics/settings.rs b/korangar/src/graphics/settings.rs similarity index 93% rename from src/graphics/settings.rs rename to korangar/src/graphics/settings.rs index ed82d6e3..4367ccec 100644 --- a/src/graphics/settings.rs +++ b/korangar/src/graphics/settings.rs @@ -1,4 +1,4 @@ -use procedural::toggle; +use korangar_procedural::toggle; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; @@ -10,8 +10,6 @@ use crate::debug::*; pub struct GraphicsSettings { #[toggle] pub frame_limit: bool, - #[toggle] - pub show_interface: bool, pub shadow_detail: ShadowDetail, } @@ -19,7 +17,6 @@ impl Default for GraphicsSettings { fn default() -> Self { Self { frame_limit: true, - show_interface: true, shadow_detail: ShadowDetail::Medium, } } diff --git a/src/graphics/smoothed.rs b/korangar/src/graphics/smoothed.rs similarity index 100% rename from src/graphics/smoothed.rs rename to korangar/src/graphics/smoothed.rs diff --git a/src/graphics/transform.rs b/korangar/src/graphics/transform.rs similarity index 97% rename from src/graphics/transform.rs rename to korangar/src/graphics/transform.rs index c65bd0ad..072613fa 100644 --- a/src/graphics/transform.rs +++ b/korangar/src/graphics/transform.rs @@ -1,7 +1,7 @@ use std::ops::Add; use cgmath::{Deg, Rad, Vector3}; -use procedural::PrototypeElement; +use korangar_procedural::PrototypeElement; use ragnarok_bytes::{ByteStream, ConversionResult, ConversionResultExt, FromBytes}; #[derive(Copy, Clone, Debug, PrototypeElement)] diff --git a/src/graphics/vertices/mod.rs b/korangar/src/graphics/vertices/mod.rs similarity index 100% rename from src/graphics/vertices/mod.rs rename to korangar/src/graphics/vertices/mod.rs diff --git a/src/graphics/vertices/model.rs b/korangar/src/graphics/vertices/model.rs similarity index 100% rename from src/graphics/vertices/model.rs rename to korangar/src/graphics/vertices/model.rs diff --git a/src/graphics/vertices/native.rs b/korangar/src/graphics/vertices/native.rs similarity index 100% rename from src/graphics/vertices/native.rs rename to korangar/src/graphics/vertices/native.rs diff --git a/src/graphics/vertices/tile.rs b/korangar/src/graphics/vertices/tile.rs similarity index 100% rename from src/graphics/vertices/tile.rs rename to korangar/src/graphics/vertices/tile.rs diff --git a/src/graphics/vertices/water.rs b/korangar/src/graphics/vertices/water.rs similarity index 100% rename from src/graphics/vertices/water.rs rename to korangar/src/graphics/vertices/water.rs diff --git a/src/input/event.rs b/korangar/src/input/event.rs similarity index 59% rename from src/input/event.rs rename to korangar/src/input/event.rs index 7bedf205..ce896333 100644 --- a/src/input/event.rs +++ b/korangar/src/input/event.rs @@ -1,7 +1,10 @@ use cgmath::Vector2; +use korangar_interface::event::ClickAction; +use korangar_interface::ElementEvent; use super::HotbarSlot; -use crate::interface::{ItemMove, SkillMove, ThemeKind}; +use crate::interface::application::{InterfaceSettings, InternalThemeKind}; +use crate::interface::resource::Move; use crate::loaders::ServiceId; use crate::network::{AccountId, CharacterId, CharacterServerInformation, EntityId}; #[cfg(feature = "debug")] @@ -20,8 +23,6 @@ pub enum UserEvent { Exit, CameraZoom(f32), CameraRotate(f32), - ToggleFrameLimit, - ToggleShowInterface, OpenMenuWindow, OpenInventoryWindow, OpenEquipmentWindow, @@ -29,15 +30,16 @@ pub enum UserEvent { OpenGraphicsSettingsWindow, OpenAudioSettingsWindow, OpenFriendsWindow, + ToggleShowInterface, SetThemeFile { theme_file: String, - theme_kind: ThemeKind, + theme_kind: InternalThemeKind, }, SaveTheme { - theme_kind: ThemeKind, + theme_kind: InternalThemeKind, }, ReloadTheme { - theme_kind: ThemeKind, + theme_kind: InternalThemeKind, }, SelectCharacter(usize), OpenCharacterCreationWindow(usize), @@ -53,8 +55,7 @@ pub enum UserEvent { NextDialog(EntityId), CloseDialog(EntityId), ChooseDialogOption(EntityId, i8), - MoveItem(ItemMove), - MoveSkill(SkillMove), + MoveResource(Move), CastSkill(HotbarSlot), StopSkill(HotbarSlot), AddFriend(String), @@ -71,10 +72,6 @@ pub enum UserEvent { character_id: CharacterId, }, #[cfg(feature = "debug")] - ToggleFrustumCulling, - #[cfg(feature = "debug")] - ToggleShowBoundingBoxes, - #[cfg(feature = "debug")] OpenMarkerDetails(MarkerIdentifier), #[cfg(feature = "debug")] OpenRenderSettingsWindow, @@ -103,8 +100,6 @@ pub enum UserEvent { #[cfg(feature = "debug")] ClearPacketHistory, #[cfg(feature = "debug")] - ToggleUseDebugCamera, - #[cfg(feature = "debug")] CameraLookAround(Vector2), #[cfg(feature = "debug")] CameraMoveForward, @@ -120,58 +115,10 @@ pub enum UserEvent { CameraAccelerate, #[cfg(feature = "debug")] CameraDecelerate, - #[cfg(feature = "debug")] - ToggleShowFramesPerSecond, - #[cfg(feature = "debug")] - ToggleShowWireframe, - #[cfg(feature = "debug")] - ToggleShowMap, - #[cfg(feature = "debug")] - ToggleShowObjects, - #[cfg(feature = "debug")] - ToggleShowEntities, - #[cfg(feature = "debug")] - ToggleShowWater, - #[cfg(feature = "debug")] - ToggleShowIndicators, - #[cfg(feature = "debug")] - ToggleShowAmbientLight, - #[cfg(feature = "debug")] - ToggleShowDirectionalLight, - #[cfg(feature = "debug")] - ToggleShowPointLights, - #[cfg(feature = "debug")] - ToggleShowParticleLights, - #[cfg(feature = "debug")] - ToggleShowDirectionalShadows, - #[cfg(feature = "debug")] - ToggleShowObjectMarkers, - #[cfg(feature = "debug")] - ToggleShowLightMarkers, - #[cfg(feature = "debug")] - ToggleShowSoundMarkers, - #[cfg(feature = "debug")] - ToggleShowEffectMarkers, - #[cfg(feature = "debug")] - ToggleShowParticleMarkers, - #[cfg(feature = "debug")] - ToggleShowEntityMarkers, - #[cfg(feature = "debug")] - ToggleShowMapTiles, - #[cfg(feature = "debug")] - ToggleShowPathing, - #[cfg(feature = "debug")] - ToggleShowDiffuseBuffer, - #[cfg(feature = "debug")] - ToggleShowNormalBuffer, - #[cfg(feature = "debug")] - ToggleShowWaterBuffer, - #[cfg(feature = "debug")] - ToggleShowDepthBuffer, - #[cfg(feature = "debug")] - ToggleShowShadowBuffer, - #[cfg(feature = "debug")] - ToggleShowPickerBuffer, - #[cfg(feature = "debug")] - ToggleShowFontAtlas, +} + +impl ElementEvent for UserEvent { + fn trigger(&mut self) -> Vec> { + vec![ClickAction::Custom(self.clone())] + } } diff --git a/src/input/key.rs b/korangar/src/input/key.rs similarity index 100% rename from src/input/key.rs rename to korangar/src/input/key.rs diff --git a/src/input/mod.rs b/korangar/src/input/mod.rs similarity index 79% rename from src/input/mod.rs rename to korangar/src/input/mod.rs index 12335660..d629bbd2 100644 --- a/src/input/mod.rs +++ b/korangar/src/input/mod.rs @@ -3,10 +3,14 @@ mod key; mod mode; use std::mem::variant_count; -use std::rc::{Rc, Weak}; use cgmath::Vector2; -use procedural::profile; +use korangar_interface::application::FocusState; +use korangar_interface::elements::{ElementCell, Focus}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainTrackedState, TrackedState}; +use korangar_interface::Interface; +use korangar_procedural::profile; use winit::dpi::PhysicalPosition; use winit::event::{ElementState, MouseButton, MouseScrollDelta, VirtualKeyCode}; @@ -16,7 +20,10 @@ pub use self::mode::{Grabbed, MouseInputMode}; #[cfg(feature = "debug")] use crate::graphics::RenderSettings; use crate::graphics::{PickerRenderTarget, PickerTarget}; -use crate::interface::{ClickAction, ElementCell, Focus, Interface, MouseCursorState, ScreenPosition, ScreenSize, WeakElementCell}; +use crate::interface::application::InterfaceSettings; +use crate::interface::cursor::{MouseCursor, MouseCursorState}; +use crate::interface::layout::{ScreenPosition, ScreenSize}; +use crate::interface::resource::PartialMove; use crate::network::ClientTick; const MOUSE_SCOLL_MULTIPLIER: f32 = 30.0; @@ -25,82 +32,6 @@ const KEY_COUNT: usize = variant_count::(); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HotbarSlot(pub usize); -#[derive(Default)] -pub struct FocusState { - focused_element: Option, - focused_window: Option, - previous_hovered_element: Option, - previous_hovered_window: Option, - previous_focused_element: Option, - previous_focused_window: Option, -} - -impl FocusState { - pub fn remove_focus(&mut self) { - self.focused_element = None; - self.focused_window = None; - } - - pub fn set_focused_element(&mut self, element: Option, window_index: usize) { - self.focused_element = element.as_ref().map(Rc::downgrade); - self.focused_window = Some(window_index); - } - - pub fn set_focused_window(&mut self, window_index: usize) { - self.focused_window = Some(window_index); - } - - pub fn update_focused_element(&mut self, element: Option, window_index: usize) { - if let Some(element) = element { - self.focused_element = Some(Rc::downgrade(&element)); - self.focused_window = Some(window_index); - } - } - - pub fn get_focused_element(&self) -> Option<(ElementCell, usize)> { - let element = self.focused_element.clone(); - element.as_ref().and_then(Weak::upgrade).zip(self.focused_window) - } - - pub fn did_hovered_element_change(&self, hovered_element: &Option) -> bool { - self.previous_hovered_element - .as_ref() - .zip(hovered_element.as_ref()) - .map(|(previous, current)| !Weak::ptr_eq(previous, &Rc::downgrade(current))) - .unwrap_or(self.previous_hovered_element.is_some() || hovered_element.is_some()) - } - - pub fn did_focused_element_change(&self) -> bool { - self.previous_focused_element - .as_ref() - .zip(self.focused_element.as_ref()) - .map(|(previous, current)| !Weak::ptr_eq(previous, current)) - .unwrap_or(self.previous_focused_element.is_some() || self.focused_element.is_some()) - } - - pub fn previous_hovered_window(&self) -> Option { - self.previous_hovered_window - } - - pub fn focused_window(&self) -> Option { - self.focused_window - } - - pub fn previous_focused_window(&self) -> Option { - self.previous_focused_window - } - - pub fn update(&mut self, hovered_element: &Option, window_index: Option) -> Option { - self.previous_hovered_element = hovered_element.as_ref().map(Rc::downgrade); - self.previous_hovered_window = window_index; - - self.previous_focused_element = self.focused_element.clone(); - self.previous_focused_window = self.focused_window; - - self.focused_element.clone().and_then(|weak_element| weak_element.upgrade()) - } -} - pub struct InputSystem { previous_mouse_position: ScreenPosition, new_mouse_position: ScreenPosition, @@ -206,13 +137,20 @@ impl InputSystem { #[profile("update user input")] pub fn user_events( &mut self, - interface: &mut Interface, - focus_state: &mut FocusState, + interface: &mut Interface, + application: &InterfaceSettings, + focus_state: &mut FocusState, picker_target: &mut PickerRenderTarget, - #[cfg(feature = "debug")] render_settings: &RenderSettings, + mouse_cursor: &mut MouseCursor, + #[cfg(feature = "debug")] render_settings: &PlainTrackedState, window_size: Vector2, client_tick: ClientTick, - ) -> (Vec, Option, Option, Option) { + ) -> ( + Vec, + Option>, + Option>, + Option, + ) { let mut events = Vec::new(); let mut mouse_target = None; let (hovered_element, mut window_index) = interface.hovered_element(self.new_mouse_position, &self.mouse_input_mode); @@ -220,7 +158,7 @@ impl InputSystem { let shift_down = self.get_key(VirtualKeyCode::LShift).down(); #[cfg(feature = "debug")] - let lock_actions = render_settings.use_debug_camera; + let lock_actions = render_settings.get().use_debug_camera; #[cfg(not(feature = "debug"))] let lock_actions = false; @@ -280,7 +218,7 @@ impl InputSystem { focus_state.update_focused_element(new_focused_element, *window_index); } - ClickAction::Event(event) => events.push(event), + ClickAction::Custom(event) => events.push(event), ClickAction::MoveInterface => self.mouse_input_mode = MouseInputMode::MoveInterface(*window_index), @@ -288,22 +226,21 @@ impl InputSystem { self.mouse_input_mode = MouseInputMode::DragElement((hovered_element.clone(), *window_index)) } - ClickAction::MoveItem(item_source, item) => { - self.mouse_input_mode = MouseInputMode::MoveItem(item_source, item); - // Needs to re-render because some elements will render differently + ClickAction::Move(drop_resource) => { + let input_mode = match drop_resource { + PartialMove::Item { source, item } => MouseInputMode::MoveItem(source, item), + PartialMove::Skill { source, skill } => MouseInputMode::MoveSkill(source, skill), + }; + self.mouse_input_mode = input_mode; + // Needs to re-render because some elements will + // render differently // based on the mouse input mode. interface.schedule_render(); } - ClickAction::MoveSkill(skill_source, skill) => { - self.mouse_input_mode = MouseInputMode::MoveSkill(skill_source, skill); - // Needs to re-render because some elements will render differently - // based on the mouse input mode. - interface.schedule_render(); + ClickAction::OpenWindow(prototype_window) => { + interface.open_window(application, focus_state, prototype_window.as_ref()) } - - ClickAction::OpenWindow(prototype_window) => interface.open_window(focus_state, prototype_window.as_ref()), - ClickAction::CloseWindow => interface.close_window(focus_state, *window_index), ClickAction::OpenPopup { @@ -331,17 +268,17 @@ impl InputSystem { interface.schedule_render(); match mouse_input_mode { - MouseInputMode::MoveItem(item_source, item) => { + MouseInputMode::MoveItem(source, item) => { if let Some(hovered_element) = &hovered_element { - if let Some(item_move) = hovered_element.borrow_mut().drop_item(item_source, item) { - events.push(UserEvent::MoveItem(item_move)); + if let Some(resource_move) = hovered_element.borrow_mut().drop_resource(PartialMove::Item { source, item }) { + events.push(UserEvent::MoveResource(resource_move)); } } } - MouseInputMode::MoveSkill(skill_source, skill) => { + MouseInputMode::MoveSkill(source, skill) => { if let Some(hovered_element) = &hovered_element { - if let Some(skill_move) = hovered_element.borrow_mut().drop_skill(skill_source, skill) { - events.push(UserEvent::MoveSkill(skill_move)); + if let Some(resource_move) = hovered_element.borrow_mut().drop_resource(PartialMove::Skill { source, skill }) { + events.push(UserEvent::MoveResource(resource_move)); } } } @@ -375,24 +312,24 @@ impl InputSystem { if self.mouse_delta != ScreenSize::default() { interface.drag_element(element, *window_index, ScreenPosition::from_size(self.mouse_delta)); } - interface.set_mouse_cursor_state(MouseCursorState::Grab, client_tick); + mouse_cursor.set_state(MouseCursorState::Grab, client_tick); } MouseInputMode::MoveInterface(identifier) => { if self.mouse_delta != ScreenSize::default() { interface.move_window(*identifier, ScreenPosition::from_size(self.mouse_delta)); } - interface.set_mouse_cursor_state(MouseCursorState::Grab, client_tick); + mouse_cursor.set_state(MouseCursorState::Grab, client_tick); } MouseInputMode::ResizeInterface(identifier) => { if self.mouse_delta != ScreenSize::default() { - interface.resize_window(*identifier, self.mouse_delta); + interface.resize_window(application, *identifier, self.mouse_delta); } } MouseInputMode::RotateCamera => { events.push(UserEvent::CameraRotate(self.mouse_delta.width)); - interface.set_mouse_cursor_state(MouseCursorState::RotateCamera, client_tick); + mouse_cursor.set_state(MouseCursorState::RotateCamera, client_tick); } - MouseInputMode::ClickInterface => interface.set_mouse_cursor_state(MouseCursorState::Click, client_tick), + MouseInputMode::ClickInterface => mouse_cursor.set_state(MouseCursorState::Click, client_tick), MouseInputMode::None => {} MouseInputMode::MoveItem(..) | MouseInputMode::MoveSkill(..) | MouseInputMode::Walk(..) => {} } @@ -430,8 +367,10 @@ impl InputSystem { for action in actions { // TODO: remove and replace with proper event match action { - ClickAction::Event(event) => events.push(event), - ClickAction::OpenWindow(prototype_window) => interface.open_window(focus_state, prototype_window.as_ref()), + ClickAction::Custom(event) => events.push(event), + ClickAction::OpenWindow(prototype_window) => { + interface.open_window(application, focus_state, prototype_window.as_ref()) + } ClickAction::CloseWindow => interface.close_window(focus_state, *focused_window), _ => {} } @@ -440,7 +379,7 @@ impl InputSystem { } if self.close_window_hotkey_pressed() && focus_state.focused_window().is_some() { - let window_index = focus_state.focused_window.unwrap(); + let window_index = focus_state.get_focused_window().unwrap(); if interface.get_window(window_index).is_closable() { interface.close_window(focus_state, window_index); } @@ -472,15 +411,16 @@ impl InputSystem { focus_state.update_focused_element(new_focused_element, *focused_window); } - ClickAction::Event(event) => events.push(event), + ClickAction::Custom(event) => events.push(event), ClickAction::MoveInterface => self.mouse_input_mode = MouseInputMode::MoveInterface(*focused_window), ClickAction::DragElement => { self.mouse_input_mode = MouseInputMode::DragElement((focused_element.clone(), *focused_window)) } // TODO: should just move immediately ? - ClickAction::MoveItem(..) => {} - ClickAction::MoveSkill(..) => {} - ClickAction::OpenWindow(prototype_window) => interface.open_window(focus_state, prototype_window.as_ref()), + ClickAction::Move(..) => {} + ClickAction::OpenWindow(prototype_window) => { + interface.open_window(application, focus_state, prototype_window.as_ref()) + } ClickAction::CloseWindow => interface.close_window(focus_state, *focused_window), ClickAction::OpenPopup { element, @@ -555,18 +495,12 @@ impl InputSystem { } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::LShift).pressed() && render_settings.use_debug_camera { + if self.get_key(VirtualKeyCode::LShift).pressed() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraAccelerate); } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::LShift).released() && render_settings.use_debug_camera { - events.push(UserEvent::CameraDecelerate); - } - - #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::F).pressed() { - events.push(UserEvent::ToggleUseDebugCamera); + if self.get_key(VirtualKeyCode::LShift).released() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraDecelerate); } @@ -574,7 +508,7 @@ impl InputSystem { if self.right_mouse_button.down() && !self.right_mouse_button.pressed() && self.mouse_input_mode.is_none() - && render_settings.use_debug_camera + && render_settings.get().use_debug_camera { events.push(UserEvent::CameraLookAround(-Vector2::new( self.mouse_delta.width, @@ -583,27 +517,27 @@ impl InputSystem { } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::W).down() && render_settings.use_debug_camera { + if self.get_key(VirtualKeyCode::W).down() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraMoveForward); } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::S).down() && render_settings.use_debug_camera { + if self.get_key(VirtualKeyCode::S).down() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraMoveBackward); } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::A).down() && render_settings.use_debug_camera { + if self.get_key(VirtualKeyCode::A).down() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraMoveLeft); } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::D).down() && render_settings.use_debug_camera { + if self.get_key(VirtualKeyCode::D).down() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraMoveRight); } #[cfg(feature = "debug")] - if self.get_key(VirtualKeyCode::Space).down() && render_settings.use_debug_camera { + if self.get_key(VirtualKeyCode::Space).down() && render_settings.get().use_debug_camera { events.push(UserEvent::CameraMoveUp); } } @@ -658,7 +592,7 @@ impl InputSystem { // cursor and then immediately over a different one that doesn't, // because main wont set the default cursor if self.mouse_input_mode.is_none() && !matches!(mouse_target, Some(PickerTarget::Entity(_))) { - interface.set_mouse_cursor_state(MouseCursorState::Default, client_tick); + mouse_cursor.set_state(MouseCursorState::Default, client_tick); } if focus_state.did_hovered_element_change(&hovered_element) { diff --git a/src/input/mode.rs b/korangar/src/input/mode.rs similarity index 62% rename from src/input/mode.rs rename to korangar/src/input/mode.rs index 73e9814f..67e2e4a5 100644 --- a/src/input/mode.rs +++ b/korangar/src/input/mode.rs @@ -1,9 +1,12 @@ use std::sync::Arc; use cgmath::Vector2; +use korangar_interface::application::MouseInputModeTrait; +use korangar_interface::elements::{Element, ElementCell}; use vulkano::image::view::ImageView; -use crate::interface::{ElementCell, ItemSource, SkillSource}; +use crate::interface::application::InterfaceSettings; +use crate::interface::resource::{ItemSource, SkillSource}; use crate::inventory::{Item, Skill}; use crate::loaders::{Actions, AnimationState, Sprite}; @@ -13,7 +16,7 @@ pub enum MouseInputMode { MoveSkill(SkillSource, Skill), MoveInterface(usize), ResizeInterface(usize), - DragElement((ElementCell, usize)), + DragElement((ElementCell, usize)), ClickInterface, RotateCamera, Walk(Vector2), @@ -47,3 +50,13 @@ impl MouseInputMode { } } } + +impl MouseInputModeTrait for MouseInputMode { + fn is_none(&self) -> bool { + matches!(self, MouseInputMode::None) + } + + fn is_self_dragged(&self, element: &dyn Element) -> bool { + matches!(self, Self::DragElement(dragged_element) if std::ptr::eq((&*dragged_element.0.borrow()) as *const _ as *const (), element as *const _ as *const ())) + } +} diff --git a/korangar/src/interface/application.rs b/korangar/src/interface/application.rs new file mode 100644 index 00000000..5987299f --- /dev/null +++ b/korangar/src/interface/application.rs @@ -0,0 +1,362 @@ +use std::marker::ConstParamTy; + +use korangar_interface::application::{Application, ScalingTrait}; +use korangar_interface::elements::{Container, ElementCell, ElementWrap, PickList, PrototypeElement, Text}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainTrackedState, TrackedStateClone}; +use korangar_interface::windows::PrototypeWindow; +use korangar_procedural::{dimension_bound, profile, PrototypeElement}; +use ron::ser::PrettyConfig; +use serde::{Deserialize, Serialize}; +use walkdir::WalkDir; + +use super::elements::MutableRange; +use super::layout::{CornerRadius, PartialScreenSize, ScreenClip, ScreenPosition, ScreenSize}; +use super::resource::{Move, PartialMove}; +use super::theme::{DefaultMain, DefaultMenu, GameTheme, InterfaceTheme, InterfaceThemeKind, Themes}; +use super::windows::WindowCache; +use crate::graphics::{Color, InterfaceRenderer}; +use crate::input::{MouseInputMode, UserEvent}; +use crate::loaders::{FontLoader, FontSize, Scaling}; + +impl korangar_interface::application::ColorTrait for Color { + fn is_transparent(&self) -> bool { + const TRANSPARENCY_THRESHOLD: f32 = 0.999; + self.alpha < TRANSPARENCY_THRESHOLD + } +} + +impl korangar_interface::application::SizeTrait for ScreenSize { + fn new(width: f32, height: f32) -> Self { + ScreenSize { width, height } + } + + fn width(&self) -> f32 { + self.width + } + + fn height(&self) -> f32 { + self.height + } +} + +impl korangar_interface::application::PositionTrait for ScreenPosition { + fn new(left: f32, top: f32) -> Self { + ScreenPosition { left, top } + } + + fn left(&self) -> f32 { + self.left + } + + fn top(&self) -> f32 { + self.top + } +} + +impl korangar_interface::application::ClipTrait for ScreenClip { + fn new(left: f32, top: f32, right: f32, bottom: f32) -> Self { + Self { left, right, top, bottom } + } + + fn left(&self) -> f32 { + self.left + } + + fn right(&self) -> f32 { + self.right + } + + fn top(&self) -> f32 { + self.top + } + + fn bottom(&self) -> f32 { + self.bottom + } +} + +impl korangar_interface::application::CornerRadiusTrait for CornerRadius { + fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self { + Self { + top_left, + top_right, + bottom_left, + bottom_right, + } + } + + fn top_left(&self) -> f32 { + self.top_left + } + + fn top_right(&self) -> f32 { + self.top_right + } + + fn bottom_right(&self) -> f32 { + self.bottom_right + } + + fn bottom_left(&self) -> f32 { + self.bottom_left + } +} + +impl korangar_interface::application::PartialSizeTrait for PartialScreenSize { + fn new(width: f32, height: Option) -> Self { + Self { width, height } + } + + fn width(&self) -> f32 { + self.width + } + + fn height(&self) -> Option { + self.height + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InternalThemeKind { + Main, + Menu, + Game, +} + +impl ConstParamTy for InternalThemeKind {} + +#[derive(Serialize, Deserialize)] +#[serde(transparent)] +pub struct ThemeSelector(pub String); + +impl ThemeSelector { + pub fn get_file(&self) -> &str { + &self.0 + } + + pub fn set_file(&mut self, file: String) { + self.0 = file; + } +} + +impl PrototypeElement for ThemeSelector { + fn to_element(&self, display: String) -> ElementCell { + let state = PlainTrackedState::new(self.0.clone()); + + let themes = WalkDir::new("client/themes/") + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_type().is_file()) + .filter_map(|path| { + let name = path.path().file_name()?.to_str()?.strip_suffix(".ron")?.to_owned(); + let file_path = format!("client/themes/{}.ron", name); + Some((name, file_path)) + }) + .collect(); + + let elements = vec![ + Text::default().with_text(display).with_width(dimension_bound!(50%)).wrap(), + PickList::default() + .with_options(themes) + .with_selected(state.clone()) + .with_event(move || { + vec![ClickAction::Custom(UserEvent::SetThemeFile { + theme_file: state.cloned(), + theme_kind: KIND, + })] + }) + .with_width(dimension_bound!(!)) + .wrap(), + ]; + + Container::new(elements).wrap() + } +} + +#[derive(Serialize, Deserialize)] +struct InterfaceSettingsStorage { + menu_theme: String, + main_theme: String, + game_theme: String, + scaling: Scaling, +} + +impl Default for InterfaceSettingsStorage { + fn default() -> Self { + let main_theme = "client/themes/main.ron".to_string(); + let menu_theme = "client/themes/menu.ron".to_string(); + let game_theme = "client/themes/game.ron".to_string(); + let scaling = Scaling::new(1.0); + + Self { + main_theme, + menu_theme, + game_theme, + scaling, + } + } +} + +impl InterfaceSettingsStorage { + pub fn load_or_default() -> Self { + Self::load().unwrap_or_else(|| { + #[cfg(feature = "debug")] + crate::debug::print_debug!( + "failed to load interface settings from {}filename{}", + crate::debug::MAGENTA, + crate::debug::NONE + ); + + Default::default() + }) + } + + pub fn load() -> Option { + #[cfg(feature = "debug")] + crate::debug::print_debug!( + "loading interface settings from {}filename{}", + crate::debug::MAGENTA, + crate::debug::NONE + ); + + std::fs::read_to_string("client/interface_settings.ron") + .ok() + .and_then(|data| ron::from_str(&data).ok()) + } + + pub fn save(&self) { + #[cfg(feature = "debug")] + crate::debug::print_debug!( + "saving interface settings to {}filename{}", + crate::debug::MAGENTA, + crate::debug::NONE + ); + + let data = ron::ser::to_string_pretty(self, PrettyConfig::new()).unwrap(); + std::fs::write("client/interface_settings.ron", data).expect("unable to write file"); + } +} + +#[derive(PrototypeElement)] +pub struct InterfaceSettings { + #[name("Main theme")] + pub main_theme: ThemeSelector<{ InternalThemeKind::Main }>, + #[name("Menu theme")] + pub menu_theme: ThemeSelector<{ InternalThemeKind::Menu }>, + #[name("Game theme")] + pub game_theme: ThemeSelector<{ InternalThemeKind::Game }>, + scaling: MutableRange, + #[hidden_element] + themes: Themes, +} + +impl InterfaceSettings { + pub fn new() -> Self { + let InterfaceSettingsStorage { + menu_theme, + main_theme, + game_theme, + scaling, + } = InterfaceSettingsStorage::load_or_default(); + + let themes = Themes::new( + InterfaceTheme::new::(&menu_theme), + InterfaceTheme::new::(&main_theme), + GameTheme::new(&menu_theme), + ); + + Self { + main_theme: ThemeSelector(main_theme), + menu_theme: ThemeSelector(menu_theme), + game_theme: ThemeSelector(game_theme), + scaling: MutableRange::new(scaling, Scaling::new(0.5), Scaling::new(2.5)), + themes, + } + } + + // TODO: Remove + pub fn get_scaling_factor(&self) -> f32 { + self.scaling.get().get_factor() + } + + pub fn theme_window(&self) -> &dyn PrototypeWindow { + &self.themes + } + + pub fn get_game_theme(&self) -> &GameTheme { + &self.themes.game + } +} + +impl InterfaceSettings { + #[profile] + pub fn set_theme_file(&mut self, theme_file: String, kind: InternalThemeKind) { + match kind { + InternalThemeKind::Menu => self.menu_theme.set_file(theme_file), + InternalThemeKind::Main => self.main_theme.set_file(theme_file), + InternalThemeKind::Game => self.game_theme.set_file(theme_file), + } + } + + #[profile] + pub fn save_theme(&self, kind: InternalThemeKind) { + match kind { + InternalThemeKind::Menu => self.themes.menu.save(self.menu_theme.get_file()), + InternalThemeKind::Main => self.themes.main.save(self.main_theme.get_file()), + InternalThemeKind::Game => self.themes.game.save(self.game_theme.get_file()), + } + } + + #[profile] + pub fn reload_theme(&mut self, kind: InternalThemeKind) { + match kind { + InternalThemeKind::Menu => self.themes.menu.reload::(self.menu_theme.get_file()), + InternalThemeKind::Main => self.themes.main.reload::(self.main_theme.get_file()), + InternalThemeKind::Game => self.themes.game.reload(self.game_theme.get_file()), + } + } +} + +impl Application for InterfaceSettings { + type Cache = WindowCache; + type Clip = ScreenClip; + type Color = Color; + type CornerRadius = CornerRadius; + type CustomEvent = UserEvent; + type DropResource = PartialMove; + type DropResult = Move; + type FontLoader = std::rc::Rc>; + type FontSize = FontSize; + type MouseInputMode = MouseInputMode; + type PartialSize = PartialScreenSize; + type Position = ScreenPosition; + type Renderer = InterfaceRenderer; + type Scaling = Scaling; + type Size = ScreenSize; + type Theme = InterfaceTheme; + type ThemeKind = InterfaceThemeKind; + + fn get_scaling(&self) -> Self::Scaling { + self.scaling.get() + } + + fn get_theme(&self, kind: &InterfaceThemeKind) -> &InterfaceTheme { + match kind { + InterfaceThemeKind::Menu => &self.themes.menu, + InterfaceThemeKind::Main => &self.themes.main, + } + } +} + +impl Drop for InterfaceSettings { + fn drop(&mut self) { + InterfaceSettingsStorage { + menu_theme: self.menu_theme.get_file().to_owned(), + main_theme: self.main_theme.get_file().to_owned(), + game_theme: self.game_theme.get_file().to_owned(), + scaling: self.scaling.get(), + } + .save(); + } +} diff --git a/src/interface/cursor/mod.rs b/korangar/src/interface/cursor/mod.rs similarity index 85% rename from src/interface/cursor/mod.rs rename to korangar/src/interface/cursor/mod.rs index 3f000092..af392757 100644 --- a/src/interface/cursor/mod.rs +++ b/korangar/src/interface/cursor/mod.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use super::{InterfaceSettings, ScreenClip, ScreenPosition, ScreenSize}; +use super::application::InterfaceSettings; +use super::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::graphics::{Color, DeferredRenderer, Renderer, SpriteRenderer}; use crate::input::Grabbed; use crate::loaders::{ActionLoader, Actions, AnimationState, GameFileLoader, Sprite, SpriteLoader}; @@ -29,6 +30,7 @@ pub struct MouseCursor { sprite: Arc, actions: Arc, animation_state: AnimationState, + shown: bool, } impl MouseCursor { @@ -36,14 +38,24 @@ impl MouseCursor { let sprite = sprite_loader.get("cursors.spr", game_file_loader).unwrap(); let actions = action_loader.get("cursors.act", game_file_loader).unwrap(); let animation_state = AnimationState::new(ClientTick(0)); + let shown = true; Self { sprite, actions, animation_state, + shown, } } + pub fn hide(&mut self) { + self.shown = false; + } + + pub fn show(&mut self) { + self.shown = true; + } + pub fn update(&mut self, client_tick: ClientTick) { self.animation_state.update(client_tick); } @@ -71,15 +83,19 @@ impl MouseCursor { mouse_position: ScreenPosition, grabbed: Option, color: Color, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, ) { + if !self.shown { + return; + } + if let Some(grabbed) = grabbed { match grabbed { Grabbed::Texture(texture) => renderer.render_sprite( render_target, texture, - mouse_position - ScreenSize::uniform(15.0 * interface_settings.scaling.get()), - ScreenSize::uniform(30.0 * interface_settings.scaling.get()), + mouse_position - ScreenSize::uniform(15.0 * application.get_scaling_factor()), + ScreenSize::uniform(30.0 * application.get_scaling_factor()), ScreenClip::default(), Color::monochrome_u8(255), false, @@ -92,7 +108,7 @@ impl MouseCursor { mouse_position, 0, Color::monochrome_u8(255), - interface_settings, + application, ), } } @@ -111,7 +127,7 @@ impl MouseCursor { mouse_position, direction, color, - interface_settings, + application, ); } } diff --git a/korangar/src/interface/dialog.rs b/korangar/src/interface/dialog.rs new file mode 100644 index 00000000..caaba6fe --- /dev/null +++ b/korangar/src/interface/dialog.rs @@ -0,0 +1,77 @@ +use derive_new::new; +use korangar_interface::state::{PlainTrackedState, TrackedStateExt, TrackedStateVec}; + +use super::elements::DialogElement; +use super::windows::DialogWindow; +use crate::network::EntityId; + +#[derive(new)] +struct DialogHandle { + elements: PlainTrackedState>, + clear: bool, +} + +#[derive(Default)] +pub struct DialogSystem { + dialog_handle: Option, +} + +impl DialogSystem { + #[korangar_procedural::profile] + pub fn open_dialog_window(&mut self, text: String, npc_id: EntityId) -> Option { + if let Some(dialog_handle) = &mut self.dialog_handle { + dialog_handle.elements.mutate(|elements| { + if dialog_handle.clear { + elements.clear(); + dialog_handle.clear = false; + } + + elements.push(DialogElement::Text(text)); + }); + + None + } else { + let (window, elements) = DialogWindow::new(text, npc_id); + self.dialog_handle = Some(DialogHandle::new(elements, false)); + + Some(window) + } + } + + #[korangar_procedural::profile] + pub fn add_next_button(&mut self) { + if let Some(dialog_handle) = &mut self.dialog_handle { + dialog_handle.elements.push(DialogElement::NextButton); + dialog_handle.clear = true; + } + } + + #[korangar_procedural::profile] + pub fn add_close_button(&mut self) { + if let Some(dialog_handle) = &mut self.dialog_handle { + dialog_handle.elements.mutate(|elements| { + elements.retain(|element| *element != DialogElement::NextButton); + elements.push(DialogElement::CloseButton); + }); + } + } + + #[korangar_procedural::profile] + pub fn add_choice_buttons(&mut self, choices: Vec) { + if let Some(dialog_handle) = &mut self.dialog_handle { + dialog_handle.elements.mutate(move |elements| { + elements.retain(|element| *element != DialogElement::NextButton); + + choices + .into_iter() + .enumerate() + .for_each(|(index, choice)| elements.push(DialogElement::ChoiceButton(choice, index as i8 + 1))); + }); + } + } + + #[korangar_procedural::profile] + pub fn close_dialog(&mut self) { + self.dialog_handle = None; + } +} diff --git a/src/interface/elements/containers/character.rs b/korangar/src/interface/elements/containers/character.rs similarity index 63% rename from src/interface/elements/containers/character.rs rename to korangar/src/interface/elements/containers/character.rs index 5befe8bd..b4aaeb4d 100644 --- a/src/interface/elements/containers/character.rs +++ b/korangar/src/interface/elements/containers/character.rs @@ -1,24 +1,36 @@ use std::cell::RefCell; use std::rc::Weak; -use procedural::{dimension_bound, size_bound}; +use korangar_interface::application::FontSizeTrait; +use korangar_interface::elements::{ButtonBuilder, ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, Text}; +use korangar_interface::event::{ChangeEvent, ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::{dimension_bound, size_bound}; use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::{MouseInputMode, UserEvent}; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::theme::InterfaceTheme; +use crate::loaders::FontSize; use crate::network::CharacterInformation; // TODO: rework all of this pub struct CharacterPreview { - characters: Remote>, - move_request: Remote>, + characters: PlainRemote>, + move_request: PlainRemote>, slot: usize, - state: ContainerState, + state: ContainerState, } impl CharacterPreview { - fn get_elements(characters: &Remote>, move_request: &Remote>, slot: usize) -> Vec { - if let Some(origin_slot) = *move_request.borrow() { + fn get_elements( + characters: &PlainRemote>, + move_request: &PlainRemote>, + slot: usize, + ) -> Vec> { + if let Some(origin_slot) = *move_request.get() { let text = match origin_slot == slot { true => "Click to cancel", false => "Switch", @@ -32,7 +44,7 @@ impl CharacterPreview { ]; } - let characters = characters.borrow(); + let characters = characters.get(); let character_information = characters.iter().find(|character| character.character_number as usize == slot); if let Some(character_information) = character_information { @@ -40,7 +52,7 @@ impl CharacterPreview { Text::default() .with_text(character_information.name.clone()) .with_foreground_color(|_| Color::rgb_u8(220, 210, 210)) - .with_font_size(|_| 18.0) + .with_font_size(|_| FontSize::new(18.0)) .wrap(), ButtonBuilder::new() .with_text("Switch") @@ -52,8 +64,8 @@ impl CharacterPreview { ButtonBuilder::new() .with_text("Delete") .with_event(UserEvent::DeleteCharacter(character_information.character_id)) - .with_background_color(|theme| theme.close_button.background_color.get()) - .with_foreground_color(|theme| theme.close_button.foreground_color.get()) + .with_background_color(|theme: &InterfaceTheme| theme.close_button.background_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.close_button.foreground_color.get()) .with_width_bound(dimension_bound!(50%)) .build() .wrap(), @@ -68,7 +80,7 @@ impl CharacterPreview { ] } - pub fn new(characters: Remote>, move_request: Remote>, slot: usize) -> Self { + pub fn new(characters: PlainRemote>, move_request: PlainRemote>, slot: usize) -> Self { let elements = Self::get_elements(&characters, &move_request, slot); let state = ContainerState::new(elements); @@ -85,16 +97,20 @@ impl CharacterPreview { } } -impl Element for CharacterPreview { - fn get_state(&self) -> &ElementState { +impl Element for CharacterPreview { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: Weak>, weak_parent: Option>>) { + fn link_back( + &mut self, + weak_self: Weak>>, + weak_parent: Option>>>, + ) { self.state.link_back(weak_self, weak_parent); } @@ -102,19 +118,24 @@ impl Element for CharacterPreview { self.state.is_focusable::() } - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option, focus: Focus) -> Option { + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { self.state.focus_next::(self_cell, caller_cell, focus) } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(20%, 150); - self.state.resolve( - placement_resolver, - interface_settings, - theme, - size_bound, - ScreenSize::uniform(4.0), - ); + self.state + .resolve(placement_resolver, application, theme, size_bound, ScreenSize::uniform(4.0)); } fn update(&mut self) -> Option { @@ -137,14 +158,14 @@ impl Element for CharacterPreview { None } - fn left_click(&mut self, _update: &mut bool) -> Vec { - if let Some(origin_slot) = *self.move_request.borrow() { + fn left_click(&mut self, _update: &mut bool) -> Vec> { + if let Some(origin_slot) = *self.move_request.get() { let event = match origin_slot == self.slot { true => UserEvent::CancelSwitchCharacterSlot, false => UserEvent::SwitchCharacterSlot(self.slot), }; - return vec![ClickAction::Event(event)]; + return vec![ClickAction::Custom(event)]; } let event = match self.has_character() { @@ -152,10 +173,10 @@ impl Element for CharacterPreview { false => UserEvent::OpenCharacterCreationWindow(self.slot), }; - vec![ClickAction::Event(event)] + vec![ClickAction::Custom(event)] } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, true), _ => HoverInformation::Missed, @@ -166,20 +187,19 @@ impl Element for CharacterPreview { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { true => theme.button.hovered_background_color.get(), @@ -190,8 +210,7 @@ impl Element for CharacterPreview { self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/src/interface/elements/containers/dialog.rs b/korangar/src/interface/elements/containers/dialog.rs similarity index 61% rename from src/interface/elements/containers/dialog.rs rename to korangar/src/interface/elements/containers/dialog.rs index 887c2996..d606c0ec 100644 --- a/src/interface/elements/containers/dialog.rs +++ b/korangar/src/interface/elements/containers/dialog.rs @@ -1,8 +1,15 @@ -use procedural::size_bound; +use korangar_interface::elements::{ButtonBuilder, ContainerState, Element, ElementCell, ElementState, ElementWrap, Text, WeakElementCell}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::{MouseInputMode, UserEvent}; -use crate::interface::{Element, *}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::theme::InterfaceTheme; +use crate::network::EntityId; #[derive(Clone, PartialEq, Eq)] pub enum DialogElement { @@ -13,13 +20,13 @@ pub enum DialogElement { } pub struct DialogContainer { - dialog_elements: Remote>, + dialog_elements: PlainRemote>, npc_id: EntityId, - state: ContainerState, + state: ContainerState, } impl DialogContainer { - fn to_element(dialog_element: &DialogElement, npc_id: EntityId) -> ElementCell { + fn to_element(dialog_element: &DialogElement, npc_id: EntityId) -> ElementCell { match dialog_element { DialogElement::Text(text) => Text::default() .with_text(text.clone()) @@ -43,9 +50,9 @@ impl DialogContainer { } } - pub fn new(dialog_elements: Remote>, npc_id: EntityId) -> Self { + pub fn new(dialog_elements: PlainRemote>, npc_id: EntityId) -> Self { let elements = dialog_elements - .borrow() + .get() .iter() .map(|element| Self::to_element(element, npc_id)) .collect(); @@ -60,30 +67,30 @@ impl DialogContainer { } } -impl Element for DialogContainer { - fn get_state(&self) -> &ElementState { +impl Element for DialogContainer { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option) { + fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { self.state.link_back(weak_self, weak_parent); } // TODO: focus related things - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, ?); - self.state.resolve( - placement_resolver, - interface_settings, - theme, - size_bound, - ScreenSize::uniform(3.0), - ); + self.state + .resolve(placement_resolver, application, theme, size_bound, ScreenSize::uniform(3.0)); } fn update(&mut self) -> Option { @@ -98,7 +105,7 @@ impl Element for DialogContainer { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { self.state.hovered_element(mouse_position, mouse_mode, false) } @@ -106,25 +113,23 @@ impl Element for DialogContainer { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/src/interface/elements/containers/equipment.rs b/korangar/src/interface/elements/containers/equipment.rs similarity index 59% rename from src/interface/elements/containers/equipment.rs rename to korangar/src/interface/elements/containers/equipment.rs index ee368526..f68cfb32 100644 --- a/src/interface/elements/containers/equipment.rs +++ b/korangar/src/interface/elements/containers/equipment.rs @@ -1,19 +1,28 @@ -use procedural::{dimension_bound, size_bound}; - -use crate::graphics::{InterfaceRenderer, Renderer}; +use korangar_interface::elements::{ + Container, ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, Text, WeakElementCell, +}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::{dimension_bound, size_bound}; + +use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::ItemBox; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::resource::ItemSource; +use crate::interface::theme::InterfaceTheme; use crate::inventory::Item; use crate::network::EquipPosition; pub struct EquipmentContainer { - items: Remote>, - weak_self: Option, // TODO: maybe remove? - state: ContainerState, + items: PlainRemote>, + state: ContainerState, } impl EquipmentContainer { - pub fn new(items: Remote>) -> Self { + pub fn new(items: PlainRemote>) -> Self { const SLOT_POSITIONS: [EquipPosition; 9] = [ EquipPosition::HeadTop, EquipPosition::HeadMiddle, @@ -27,7 +36,7 @@ impl EquipmentContainer { ]; let elements = { - let items = items.borrow(); + let items = items.get(); (0..SLOT_POSITIONS.len()) .map(|index| { @@ -52,24 +61,22 @@ impl EquipmentContainer { .collect() }; - let weak_self = None; let state = ContainerState::new(elements); - Self { items, weak_self, state } + Self { items, state } } } -impl Element for EquipmentContainer { - fn get_state(&self) -> &ElementState { +impl Element for EquipmentContainer { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option) { - self.weak_self = Some(weak_self.clone()); + fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { self.state.link_back(weak_self, weak_parent); } @@ -77,29 +84,34 @@ impl Element for EquipmentContainer { self.state.is_focusable::() } - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option, focus: Focus) -> Option { + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { self.state.focus_next::(self_cell, caller_cell, focus) } - fn restore_focus(&self, self_cell: ElementCell) -> Option { + fn restore_focus(&self, self_cell: ElementCell) -> Option> { self.state.restore_focus(self_cell) } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, ?); - self.state.resolve( - placement_resolver, - interface_settings, - theme, - size_bound, - ScreenSize::uniform(3.0), - ); + self.state + .resolve(placement_resolver, application, theme, size_bound, ScreenSize::uniform(3.0)); } fn update(&mut self) -> Option { if self.items.consume_changed() { let weak_parent = self.state.state.parent_element.take(); - let weak_self = self.weak_self.take().unwrap(); + let weak_self = self.state.state.self_element.take().unwrap(); *self = Self::new(self.items.clone()); // important: link back after creating elements, otherwise focus navigation and @@ -112,7 +124,7 @@ impl Element for EquipmentContainer { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::MoveItem(..) | MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), _ => HoverInformation::Missed, @@ -123,25 +135,23 @@ impl Element for EquipmentContainer { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/korangar/src/interface/elements/containers/friends.rs b/korangar/src/interface/elements/containers/friends.rs new file mode 100644 index 00000000..fa98be12 --- /dev/null +++ b/korangar/src/interface/elements/containers/friends.rs @@ -0,0 +1,182 @@ +use std::cell::{RefCell, UnsafeCell}; +use std::rc::{Rc, Weak}; + +use korangar_interface::elements::{ + ButtonBuilder, ContainerState, Element, ElementCell, ElementState, ElementWrap, Expandable, Focus, WeakElementCell, +}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; + +use crate::graphics::{InterfaceRenderer, Renderer}; +use crate::input::{MouseInputMode, UserEvent}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::theme::InterfaceTheme; +use crate::network::Friend; + +pub struct FriendView { + friends: PlainRemote>>)>>, + state: ContainerState, +} + +impl FriendView { + pub fn new(friends: PlainRemote>>)>>) -> Self { + let elements = { + let friends = friends.get(); + + friends + .iter() + .map(|(friend, linked_element)| { + let element = Self::friend_to_element(friend); + unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; + element + }) + .collect() + }; + + Self { + friends, + state: ContainerState::new(elements), + } + } + + fn friend_to_element(friend: &Friend) -> ElementCell { + let elements = vec![ + ButtonBuilder::new() + .with_text("remove") + .with_event(UserEvent::RemoveFriend { + account_id: friend.account_id, + character_id: friend.character_id, + }) + .build() + .wrap(), + ]; + + Expandable::new(friend.name.clone(), elements, false).wrap() + } +} + +impl Element for FriendView { + fn get_state(&self) -> &ElementState { + &self.state.state + } + + fn get_state_mut(&mut self) -> &mut ElementState { + &mut self.state.state + } + + fn link_back( + &mut self, + weak_self: Weak>>, + weak_parent: Option>>>, + ) { + self.state.link_back(weak_self, weak_parent); + } + + fn is_focusable(&self) -> bool { + self.state.is_focusable::() + } + + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { + self.state.focus_next::(self_cell, caller_cell, focus) + } + + fn restore_focus(&self, self_cell: ElementCell) -> Option> { + self.state.restore_focus(self_cell) + } + + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { + self.state.resolve( + placement_resolver, + application, + theme, + &size_bound!(100%, ?), + ScreenSize::default(), + ); + } + + fn update(&mut self) -> Option { + let mut resolve = false; + + if self.friends.consume_changed() { + // Remove elements of old friends from the start of the list and add new friends + // to the list. + self.friends.get().iter().enumerate().for_each(|(index, (friend, linked_element))| { + if let Some(linked_element) = unsafe { &(*linked_element.get()) } { + while !std::ptr::addr_eq(linked_element.as_ptr(), Rc::downgrade(&self.state.elements[index]).as_ptr()) { + self.state.elements.remove(index); + } + } else { + let element = Self::friend_to_element(friend); + unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; + let weak_self = self.state.state.self_element.clone(); + + element.borrow_mut().link_back(Rc::downgrade(&element), weak_self); + + self.state.elements.insert(index, element); + resolve = true; + } + }); + + // Remove elements of old friends from the end of the list. + let friend_count = self.friends.get().len(); + if friend_count < self.state.elements.len() { + self.state.elements.truncate(friend_count); + resolve = true; + } + } + + match resolve { + true => Some(ChangeEvent::RESOLVE_WINDOW), + false => None, + } + } + + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + match mouse_mode { + MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), + _ => HoverInformation::Missed, + } + } + + fn render( + &self, + render_target: &mut ::Target, + renderer: &InterfaceRenderer, + application: &InterfaceSettings, + theme: &InterfaceTheme, + parent_position: ScreenPosition, + screen_clip: ScreenClip, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, + mouse_mode: &MouseInputMode, + second_theme: bool, + ) { + let mut renderer = self + .state + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + self.state.render( + &mut renderer, + application, + theme, + hovered_element, + focused_element, + mouse_mode, + second_theme, + ); + } +} diff --git a/src/interface/elements/containers/hotbar.rs b/korangar/src/interface/elements/containers/hotbar.rs similarity index 56% rename from src/interface/elements/containers/hotbar.rs rename to korangar/src/interface/elements/containers/hotbar.rs index 160e8a27..6482234d 100644 --- a/src/interface/elements/containers/hotbar.rs +++ b/korangar/src/interface/elements/containers/hotbar.rs @@ -1,19 +1,27 @@ -use procedural::size_bound; +use korangar_interface::elements::{ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, WeakElementCell}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::{HotbarSlot, MouseInputMode}; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::SkillBox; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::resource::SkillSource; +use crate::interface::theme::InterfaceTheme; use crate::inventory::Skill; pub struct HotbarContainer { - skills: Remote<[Option; 10]>, - state: ContainerState, + skills: PlainRemote<[Option; 10]>, + state: ContainerState, } impl HotbarContainer { - pub fn new(skills: Remote<[Option; 10]>) -> Self { + pub fn new(skills: PlainRemote<[Option; 10]>) -> Self { let elements = { - let skills = skills.borrow(); + let skills = skills.get(); skills .iter() @@ -38,16 +46,16 @@ impl HotbarContainer { } } -impl Element for HotbarContainer { - fn get_state(&self) -> &ElementState { +impl Element for HotbarContainer { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option) { + fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { self.state.link_back(weak_self, weak_parent); } @@ -55,23 +63,28 @@ impl Element for HotbarContainer { self.state.is_focusable::() } - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option, focus: Focus) -> Option { + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { self.state.focus_next::(self_cell, caller_cell, focus) } - fn restore_focus(&self, self_cell: ElementCell) -> Option { + fn restore_focus(&self, self_cell: ElementCell) -> Option> { self.state.restore_focus(self_cell) } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, ?); - self.state.resolve( - placement_resolver, - interface_settings, - theme, - size_bound, - ScreenSize::uniform(3.0), - ); + self.state + .resolve(placement_resolver, application, theme, size_bound, ScreenSize::uniform(3.0)); } fn update(&mut self) -> Option { @@ -90,7 +103,7 @@ impl Element for HotbarContainer { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::MoveSkill(..) | MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), _ => HoverInformation::Missed, @@ -101,25 +114,23 @@ impl Element for HotbarContainer { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/src/interface/elements/containers/inventory.rs b/korangar/src/interface/elements/containers/inventory.rs similarity index 52% rename from src/interface/elements/containers/inventory.rs rename to korangar/src/interface/elements/containers/inventory.rs index 764a22d8..5a83d02e 100644 --- a/src/interface/elements/containers/inventory.rs +++ b/korangar/src/interface/elements/containers/inventory.rs @@ -1,25 +1,33 @@ -use procedural::size_bound; +use korangar_interface::elements::{ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, WeakElementCell}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; -use crate::graphics::{InterfaceRenderer, Renderer}; +use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::ItemBox; +use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::resource::{ItemSource, Move, PartialMove}; +use crate::interface::theme::InterfaceTheme; use crate::inventory::Item; pub struct InventoryContainer { - items: Remote>, - weak_self: Option, // TODO: maybe remove? - state: ContainerState, + items: PlainRemote>, + weak_self: Option>, // TODO: maybe remove? + state: ContainerState, } impl InventoryContainer { - pub fn new(items: Remote>) -> Self { + pub fn new(items: PlainRemote>) -> Self { let elements = { - let items = items.borrow(); + let items = items.get(); (0..40) .map(|index| items.get(index).cloned()) .map(|item| ItemBox::new(item, ItemSource::Inventory, Box::new(|_| false))) - .map(ItemBox::wrap) + .map(ElementWrap::wrap) .collect() }; @@ -30,16 +38,16 @@ impl InventoryContainer { } } -impl Element for InventoryContainer { - fn get_state(&self) -> &ElementState { +impl Element for InventoryContainer { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option) { + fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { self.weak_self = Some(weak_self.clone()); self.state.link_back(weak_self, weak_parent); } @@ -48,23 +56,28 @@ impl Element for InventoryContainer { self.state.is_focusable::() } - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option, focus: Focus) -> Option { + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { self.state.focus_next::(self_cell, caller_cell, focus) } - fn restore_focus(&self, self_cell: ElementCell) -> Option { + fn restore_focus(&self, self_cell: ElementCell) -> Option> { self.state.restore_focus(self_cell) } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, ?); - self.state.resolve( - placement_resolver, - interface_settings, - theme, - size_bound, - ScreenSize::uniform(3.0), - ); + self.state + .resolve(placement_resolver, application, theme, size_bound, ScreenSize::uniform(3.0)); } fn update(&mut self) -> Option { @@ -83,7 +96,7 @@ impl Element for InventoryContainer { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::MoveItem(..) => self.state.state.hovered_element(mouse_position), MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), @@ -91,9 +104,13 @@ impl Element for InventoryContainer { } } - fn drop_item(&mut self, item_source: ItemSource, item: Item) -> Option { - Some(ItemMove { - source: item_source, + fn drop_resource(&mut self, drop_resource: PartialMove) -> Option { + let PartialMove::Item { source, item } = drop_resource else { + return None; + }; + + (source != ItemSource::Inventory).then_some(Move::Item { + source, destination: ItemSource::Inventory, item, }) @@ -103,25 +120,23 @@ impl Element for InventoryContainer { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/korangar/src/interface/elements/containers/mod.rs b/korangar/src/interface/elements/containers/mod.rs new file mode 100644 index 00000000..7a01f8e6 --- /dev/null +++ b/korangar/src/interface/elements/containers/mod.rs @@ -0,0 +1,19 @@ +mod character; +mod dialog; +mod equipment; +mod friends; +mod hotbar; +mod inventory; +#[cfg(feature = "debug")] +mod packet; +mod skill_tree; + +pub use self::character::CharacterPreview; +pub use self::dialog::{DialogContainer, DialogElement}; +pub use self::equipment::EquipmentContainer; +pub use self::friends::FriendView; +pub use self::hotbar::HotbarContainer; +pub use self::inventory::InventoryContainer; +#[cfg(feature = "debug")] +pub use self::packet::{PacketEntry, PacketView}; +pub use self::skill_tree::SkillTreeContainer; diff --git a/src/interface/elements/containers/packet.rs b/korangar/src/interface/elements/containers/packet.rs similarity index 68% rename from src/interface/elements/containers/packet.rs rename to korangar/src/interface/elements/containers/packet.rs index ce75eb75..1e50b39e 100644 --- a/src/interface/elements/containers/packet.rs +++ b/korangar/src/interface/elements/containers/packet.rs @@ -1,25 +1,39 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::fmt::{Display, Formatter, Result}; -use std::rc::Weak; +use std::rc::{Rc, Weak}; -use procedural::size_bound; +use korangar_interface::elements::{ + ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, PrototypeElement, WeakElementCell, +}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote, RemoteClone}; +use korangar_procedural::size_bound; +use crate::debug::RingBuffer; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::{Element, *}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::theme::InterfaceTheme; struct HiddenElement; -impl Element for HiddenElement { - fn get_state(&self) -> &ElementState { +impl Element for HiddenElement { + fn get_state(&self) -> &ElementState { unimplemented!() } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { unimplemented!() } - fn resolve(&mut self, _placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, _theme: &InterfaceTheme) { + fn resolve( + &mut self, + _placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + _theme: &InterfaceTheme, + ) { unimplemented!() } @@ -27,13 +41,12 @@ impl Element for HiddenElement { &self, _render_target: &mut ::Target, _render: &InterfaceRenderer, - _state_provider: &StateProvider, - _interface_settings: &InterfaceSettings, + _application: &InterfaceSettings, _theme: &InterfaceTheme, _parent_position: ScreenPosition, _screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + _hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { @@ -56,14 +69,14 @@ impl Display for Direction { } pub struct PacketEntry { - element: Box, + element: Box>, name: &'static str, is_ping: bool, direction: Direction, } impl PacketEntry { - pub fn new_incoming(element: &(impl PrototypeElement + Clone + 'static), name: &'static str, is_ping: bool) -> Self { + pub fn new_incoming(element: &(impl PrototypeElement + Clone + 'static), name: &'static str, is_ping: bool) -> Self { Self { element: Box::new(element.clone()), name, @@ -72,7 +85,7 @@ impl PacketEntry { } } - pub fn new_outgoing(element: &(impl PrototypeElement + Clone + 'static), name: &'static str, is_ping: bool) -> Self { + pub fn new_outgoing(element: &(impl PrototypeElement + Clone + 'static), name: &'static str, is_ping: bool) -> Self { Self { element: Box::new(element.clone()), name, @@ -85,24 +98,27 @@ impl PacketEntry { self.is_ping } - fn to_element(&self) -> ElementCell { + fn to_element(&self) -> ElementCell { self.element.to_element(format!("{} {}", self.direction, self.name)) } } pub struct PacketView { - packets: Remote>), N>>, - show_pings: Remote, - hidden_element: ElementCell, - state: ContainerState, + packets: PlainRemote>>), N>>, + show_pings: PlainRemote, + hidden_element: ElementCell, + state: ContainerState, } impl PacketView { - pub fn new(packets: Remote>), N>>, show_pings: Remote) -> Self { + pub fn new( + packets: PlainRemote>>), N>>, + show_pings: PlainRemote, + ) -> Self { let hidden_element = HiddenElement.wrap(); let elements = { - let packets = packets.borrow(); - let show_pings = *show_pings.borrow(); + let packets = packets.get(); + let show_pings = show_pings.cloned(); packets .iter() @@ -133,16 +149,20 @@ impl PacketView { } } -impl Element for PacketView { - fn get_state(&self) -> &ElementState { +impl Element for PacketView { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: Weak>, weak_parent: Option>>) { + fn link_back( + &mut self, + weak_self: Weak>>, + weak_parent: Option>>>, + ) { self.state.link_back(weak_self, weak_parent); } @@ -150,18 +170,28 @@ impl Element for PacketView { self.state.is_focusable::() } - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option, focus: Focus) -> Option { + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { self.state.focus_next::(self_cell, caller_cell, focus) } - fn restore_focus(&self, self_cell: ElementCell) -> Option { + fn restore_focus(&self, self_cell: ElementCell) -> Option> { self.state.restore_focus(self_cell) } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { self.state.resolve( placement_resolver, - interface_settings, + application, theme, &size_bound!(100%, ?), ScreenSize::default(), @@ -172,7 +202,10 @@ impl Element for PacketView { let mut resolve = false; if self.show_pings.consume_changed() | self.packets.consume_changed() { - fn compare(linked_element: &UnsafeCell>, element: &ElementCell) -> bool { + fn compare( + linked_element: &UnsafeCell>>, + element: &ElementCell, + ) -> bool { let linked_element = unsafe { &*linked_element.get() }; let linked_element = linked_element.as_ref().map(|weak| weak.as_ptr()); linked_element.is_some_and(|pointer| !std::ptr::addr_eq(pointer, Rc::downgrade(element).as_ptr())) @@ -181,7 +214,7 @@ impl Element for PacketView { // Remove elements of packets that are no longer in the list. if let Some(first_visible_packet) = self .packets - .borrow() + .get() .iter() .find(|(_, linked_element)| compare(linked_element, &self.hidden_element)) { @@ -202,12 +235,12 @@ impl Element for PacketView { resolve = true; } - let show_pings = *self.show_pings.borrow(); + let show_pings = self.show_pings.cloned(); let mut index = 0; // Add or remove elements that need to be shown/hidden based on filtering. Also // append new elements for packets that are new. - self.packets.borrow().iter().for_each(|(packet, linked_element)| { + self.packets.get().iter().for_each(|(packet, linked_element)| { // Getting here means thatt the packet was already processed once. let show_packet = show_pings || !packet.is_ping(); @@ -264,7 +297,7 @@ impl Element for PacketView { } } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), _ => HoverInformation::Missed, @@ -275,25 +308,23 @@ impl Element for PacketView { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/src/interface/elements/containers/skill_tree.rs b/korangar/src/interface/elements/containers/skill_tree.rs similarity index 50% rename from src/interface/elements/containers/skill_tree.rs rename to korangar/src/interface/elements/containers/skill_tree.rs index d4bc83d4..29a000fa 100644 --- a/src/interface/elements/containers/skill_tree.rs +++ b/korangar/src/interface/elements/containers/skill_tree.rs @@ -1,20 +1,28 @@ -use procedural::size_bound; +use korangar_interface::elements::{ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, WeakElementCell}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::SkillBox; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::resource::{Move, PartialMove, SkillSource}; +use crate::interface::theme::InterfaceTheme; use crate::inventory::Skill; pub struct SkillTreeContainer { - skills: Remote>, - weak_self: Option, - state: ContainerState, + skills: PlainRemote>, + weak_self: Option>, + state: ContainerState, } impl SkillTreeContainer { - pub fn new(skills: Remote>) -> Self { + pub fn new(skills: PlainRemote>) -> Self { let elements = { - let skills = skills.borrow(); + let skills = skills.get(); skills .iter() @@ -30,16 +38,16 @@ impl SkillTreeContainer { } } -impl Element for SkillTreeContainer { - fn get_state(&self) -> &ElementState { +impl Element for SkillTreeContainer { + fn get_state(&self) -> &ElementState { &self.state.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state.state } - fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option) { + fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { self.weak_self = Some(weak_self.clone()); self.state.link_back(weak_self, weak_parent); } @@ -48,23 +56,28 @@ impl Element for SkillTreeContainer { self.state.is_focusable::() } - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option, focus: Focus) -> Option { + fn focus_next( + &self, + self_cell: ElementCell, + caller_cell: Option>, + focus: Focus, + ) -> Option> { self.state.focus_next::(self_cell, caller_cell, focus) } - fn restore_focus(&self, self_cell: ElementCell) -> Option { + fn restore_focus(&self, self_cell: ElementCell) -> Option> { self.state.restore_focus(self_cell) } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, ?); - self.state.resolve( - placement_resolver, - interface_settings, - theme, - size_bound, - ScreenSize::uniform(3.0), - ); + self.state + .resolve(placement_resolver, application, theme, size_bound, ScreenSize::uniform(3.0)); } fn update(&mut self) -> Option { @@ -83,7 +96,7 @@ impl Element for SkillTreeContainer { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::MoveItem(..) => self.state.state.hovered_element(mouse_position), MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), @@ -91,37 +104,39 @@ impl Element for SkillTreeContainer { } } - /*fn drop_skill(&mut self, skill_source: SkillSource, skill: Skill) -> Option { - Some(SkillMove { - source: skill_source, + fn drop_resource(&mut self, drop_resource: PartialMove) -> Option { + let PartialMove::Skill { source, skill } = drop_resource else { + return None; + }; + + (source != SkillSource::SkillTree).then_some(Move::Skill { + source, destination: SkillSource::SkillTree, skill, }) - }*/ + } fn render( &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, second_theme: bool, ) { let mut renderer = self .state .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); self.state.render( &mut renderer, - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/src/interface/elements/miscellanious/chat/builder.rs b/korangar/src/interface/elements/miscellanious/chat/builder.rs similarity index 78% rename from src/interface/elements/miscellanious/chat/builder.rs rename to korangar/src/interface/elements/miscellanious/chat/builder.rs index ac55488c..595bb84a 100644 --- a/src/interface/elements/miscellanious/chat/builder.rs +++ b/korangar/src/interface/elements/miscellanious/chat/builder.rs @@ -1,6 +1,11 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use korangar_interface::builder::Unset; +use korangar_interface::state::PlainRemote; + use super::Chat; -use crate::interface::builder::Unset; -use crate::interface::*; +use crate::loaders::FontLoader; use crate::network::ChatMessage; /// Type state [`Chat`] builder. This builder utilizes the type system to @@ -22,7 +27,7 @@ impl ChatBuilder { } impl ChatBuilder { - pub fn with_messages(self, messages: Remote>) -> ChatBuilder>, FONT> { + pub fn with_messages(self, messages: PlainRemote>) -> ChatBuilder>, FONT> { ChatBuilder { messages, ..self } } } @@ -33,7 +38,7 @@ impl ChatBuilder { } } -impl ChatBuilder>, Rc>> { +impl ChatBuilder>, Rc>> { /// Take the builder and turn it into a [`Chat`]. /// /// NOTE: This method is only available if diff --git a/src/interface/elements/miscellanious/chat/mod.rs b/korangar/src/interface/elements/miscellanious/chat/mod.rs similarity index 57% rename from src/interface/elements/miscellanious/chat/mod.rs rename to korangar/src/interface/elements/miscellanious/chat/mod.rs index d9f21b2d..1f686fba 100644 --- a/src/interface/elements/miscellanious/chat/mod.rs +++ b/korangar/src/interface/elements/miscellanious/chat/mod.rs @@ -1,27 +1,38 @@ mod builder; -use procedural::size_bound; +use std::cell::RefCell; +use std::rc::Rc; + +use korangar_interface::application::{Application, FontSizeTraitExt}; +use korangar_interface::elements::{Element, ElementState}; +use korangar_interface::event::ChangeEvent; +use korangar_interface::layout::{Dimension, PlacementResolver}; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; pub use self::builder::ChatBuilder; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::interface::{Element, *}; +use crate::graphics::{Color, InterfaceRenderer, Renderer}; +use crate::input::MouseInputMode; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition}; +use crate::interface::theme::InterfaceTheme; use crate::loaders::FontLoader; use crate::network::ChatMessage; pub struct Chat { - messages: Remote>, + messages: PlainRemote>, font_loader: Rc>, // TODO: make this Remote stamp: bool, - state: ElementState, + state: ElementState, } -impl Element for Chat { - fn get_state(&self) -> &ElementState { +impl Element for Chat { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } @@ -29,25 +40,30 @@ impl Element for Chat { false } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { let mut size_bound = size_bound!(100%, 0); // Not sure why but 0.0 cuts off the lower part of the text, so add some // padding. - let mut height = 5.0 * interface_settings.scaling.get(); + let mut height = 5.0 * application.get_scaling_factor(); // NOTE: Dividing by the scaling is done to counteract the scaling being applied // twice per message. It's not the cleanest solution but it works. - for message in self.messages.borrow().iter() { + for message in self.messages.get().iter() { height += self .font_loader .borrow() .get_text_dimensions( message.stamped_text(self.stamp), - theme.chat.font_size.get() * interface_settings.scaling.get(), + theme.chat.font_size.get().scaled(application.get_scaling()), placement_resolver.get_available().width, ) - .y - / interface_settings.scaling.get(); + .height + / application.get_scaling_factor(); } size_bound.height = Dimension::Absolute(height); @@ -62,23 +78,22 @@ impl Element for Chat { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + _hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let mut offset = 0.0; - for message in self.messages.borrow().iter() { + for message in self.messages.get().iter() { let text = message.stamped_text(self.stamp); renderer.render_text( @@ -98,7 +113,7 @@ impl Element for Chat { ScreenPosition::only_top(offset), message.color, theme.chat.font_size.get(), - ) / interface_settings.scaling.get(); + ) / application.get_scaling_factor(); } } } diff --git a/src/interface/elements/miscellanious/item.rs b/korangar/src/interface/elements/miscellanious/item.rs similarity index 51% rename from src/interface/elements/miscellanious/item.rs rename to korangar/src/interface/elements/miscellanious/item.rs index 1f4500a3..b872e369 100644 --- a/src/interface/elements/miscellanious/item.rs +++ b/korangar/src/interface/elements/miscellanious/item.rs @@ -1,10 +1,18 @@ use derive_new::new; -use procedural::size_bound; +use korangar_interface::application::{FontSizeTrait, SizeTraitExt}; +use korangar_interface::elements::{Element, ElementState}; +use korangar_interface::event::{ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_procedural::size_bound; -use crate::graphics::{InterfaceRenderer, Renderer}; +use crate::graphics::{Color, InterfaceRenderer, Renderer, SpriteRenderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::resource::{ItemSource, Move, PartialMove}; +use crate::interface::theme::InterfaceTheme; use crate::inventory::Item; +use crate::loaders::{FontSize, Scaling}; #[derive(new)] pub struct ItemBox { @@ -12,15 +20,15 @@ pub struct ItemBox { source: ItemSource, highlight: Box bool>, #[new(default)] - state: ElementState, + state: ElementState, } -impl Element for ItemBox { - fn get_state(&self) -> &ElementState { +impl Element for ItemBox { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } @@ -28,28 +36,40 @@ impl Element for ItemBox { self.item.is_some() } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, _theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + _theme: &InterfaceTheme, + ) { self.state.resolve(placement_resolver, &size_bound!(30, 30)); } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match self.item.is_some() || matches!(mouse_mode, MouseInputMode::MoveItem(..)) { true => self.state.hovered_element(mouse_position), false => HoverInformation::Missed, } } - fn left_click(&mut self, _force_update: &mut bool) -> Vec { + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { if let Some(item) = &self.item { - return vec![ClickAction::MoveItem(self.source, item.clone())]; + return vec![ClickAction::Move(PartialMove::Item { + source: self.source, + item: item.clone(), + })]; } Vec::new() } - fn drop_item(&mut self, item_source: ItemSource, item: Item) -> Option { - Some(ItemMove { - source: item_source, + fn drop_resource(&mut self, drop_resource: PartialMove) -> Option { + let PartialMove::Item { source, item } = drop_resource else { + return None; + }; + + (source != self.source).then_some(Move::Item { + source, destination: self.source, item, }) @@ -59,19 +79,18 @@ impl Element for ItemBox { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let highlight = (self.highlight)(mouse_mode); let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { @@ -84,11 +103,14 @@ impl Element for ItemBox { renderer.render_background(CornerRadius::uniform(5.0), background_color); if let Some(item) = &self.item { - renderer.render_sprite( + renderer.renderer.render_sprite( + renderer.render_target, item.texture.clone(), - ScreenPosition::default(), - ScreenSize::uniform(30.0), + renderer.position, + ScreenSize::uniform(30.0).scaled(Scaling::new(application.get_scaling_factor())), + renderer.clip, Color::monochrome_u8(255), + false, ); renderer.render_text( @@ -96,7 +118,7 @@ impl Element for ItemBox { "1", ScreenPosition::default(), theme.button.foreground_color.get(), - 8.0, + FontSize::new(8.0), ); } } diff --git a/korangar/src/interface/elements/miscellanious/mod.rs b/korangar/src/interface/elements/miscellanious/mod.rs new file mode 100644 index 00000000..a48bc589 --- /dev/null +++ b/korangar/src/interface/elements/miscellanious/mod.rs @@ -0,0 +1,7 @@ +mod chat; +mod item; +mod skill; + +pub use self::chat::ChatBuilder; +pub use self::item::ItemBox; +pub use self::skill::SkillBox; diff --git a/src/interface/elements/miscellanious/skill.rs b/korangar/src/interface/elements/miscellanious/skill.rs similarity index 57% rename from src/interface/elements/miscellanious/skill.rs rename to korangar/src/interface/elements/miscellanious/skill.rs index b4ae1354..3d7f0e9e 100644 --- a/src/interface/elements/miscellanious/skill.rs +++ b/korangar/src/interface/elements/miscellanious/skill.rs @@ -1,10 +1,18 @@ use derive_new::new; -use procedural::size_bound; +use korangar_interface::application::FontSizeTrait; +use korangar_interface::elements::{Element, ElementState}; +use korangar_interface::event::{ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_procedural::size_bound; -use crate::graphics::{InterfaceRenderer, Renderer}; +use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition}; +use crate::interface::resource::{Move, PartialMove, SkillSource}; +use crate::interface::theme::InterfaceTheme; use crate::inventory::Skill; +use crate::loaders::FontSize; #[derive(new)] pub struct SkillBox { @@ -12,15 +20,15 @@ pub struct SkillBox { source: SkillSource, highlight: Box bool>, #[new(default)] - state: ElementState, + state: ElementState, } -impl Element for SkillBox { - fn get_state(&self) -> &ElementState { +impl Element for SkillBox { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } @@ -28,28 +36,40 @@ impl Element for SkillBox { self.skill.is_some() } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, _theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + _theme: &InterfaceTheme, + ) { self.state.resolve(placement_resolver, &size_bound!(30, 30)); } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match self.skill.is_some() || matches!(mouse_mode, MouseInputMode::MoveSkill(..)) { true => self.state.hovered_element(mouse_position), false => HoverInformation::Missed, } } - fn left_click(&mut self, _force_update: &mut bool) -> Vec { + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { if let Some(skill) = &self.skill { - return vec![ClickAction::MoveSkill(self.source, skill.clone())]; + return vec![ClickAction::Move(PartialMove::Skill { + source: self.source, + skill: skill.clone(), + })]; } Vec::new() } - fn drop_skill(&mut self, skill_source: SkillSource, skill: Skill) -> Option { - (skill_source != self.source).then_some(SkillMove { - source: skill_source, + fn drop_resource(&mut self, drop_resource: PartialMove) -> Option { + let PartialMove::Skill { source, skill } = drop_resource else { + return None; + }; + + (source != self.source).then_some(Move::Skill { + source, destination: self.source, skill, }) @@ -59,19 +79,18 @@ impl Element for SkillBox { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let highlight = (self.highlight)(mouse_mode); let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { @@ -89,23 +108,23 @@ impl Element for SkillBox { renderer.renderer, &skill.sprite, &skill.animation_state, - renderer.position + ScreenPosition::uniform(15.0 * interface_settings.scaling.get()), + renderer.position + ScreenPosition::uniform(15.0 * application.get_scaling_factor()), 0, Color::monochrome_u8(255), - interface_settings, + application, ); renderer.render_text( &format!("{}", skill.skill_level.0), ScreenPosition::uniform(1.0), Color::monochrome_u8(0), - 15.0, + FontSize::new(15.0), ); renderer.render_text( &format!("{}", skill.skill_level.0), ScreenPosition::default(), Color::monochrome_u8(255), - 15.0, + FontSize::new(15.0), ); } } diff --git a/src/interface/elements/mod.rs b/korangar/src/interface/elements/mod.rs similarity index 76% rename from src/interface/elements/mod.rs rename to korangar/src/interface/elements/mod.rs index 17fef7f2..8ec18663 100644 --- a/src/interface/elements/mod.rs +++ b/korangar/src/interface/elements/mod.rs @@ -1,24 +1,17 @@ -#[macro_use] -mod base; -mod buttons; mod containers; mod miscellanious; mod mutable; mod mutable_range; #[cfg(feature = "debug")] mod profiler; -mod prototype; mod values; mod wrappers; -pub use self::base::*; -pub use self::buttons::*; pub use self::containers::*; pub use self::miscellanious::*; pub use self::mutable::PrototypeMutableElement; pub use self::mutable_range::PrototypeMutableRangeElement; #[cfg(feature = "debug")] pub use self::profiler::*; -pub use self::prototype::*; pub use self::values::*; pub use self::wrappers::*; diff --git a/src/interface/elements/mutable.rs b/korangar/src/interface/elements/mutable.rs similarity index 71% rename from src/interface/elements/mutable.rs rename to korangar/src/interface/elements/mutable.rs index 67adf5ad..6589cd30 100644 --- a/src/interface/elements/mutable.rs +++ b/korangar/src/interface/elements/mutable.rs @@ -1,12 +1,17 @@ +use korangar_interface::elements::{Container, ElementCell, ElementWrap, StaticLabel}; +use korangar_interface::event::ChangeEvent; +use korangar_interface::layout::{DimensionBound, SizeBound}; + +use super::MutableColorValue; use crate::graphics::Color; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; pub trait PrototypeMutableElement { - fn to_mutable_element(&self, display: String, change_event: Option) -> ElementCell; + fn to_mutable_element(&self, display: String, change_event: Option) -> ElementCell; } impl PrototypeMutableElement for Color { - fn to_mutable_element(&self, display: String, change_event: Option) -> ElementCell { + fn to_mutable_element(&self, display: String, change_event: Option) -> ElementCell { // SAFETY: This is obviously unsafe, so one needs to make sure that the element // implementing `PrototypeMutableElement` will be valid and pinned while this // element exists. Additionally, it should only be used in a debug @@ -23,7 +28,7 @@ impl PrototypeMutableElement for Color { } impl PrototypeMutableElement for DimensionBound { - fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { + fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { let elements = vec![StaticLabel::new(display).wrap()]; Container::new(elements).wrap() @@ -31,7 +36,7 @@ impl PrototypeMutableElement for DimensionBound { } impl PrototypeMutableElement for SizeBound { - fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { + fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { let elements = vec![StaticLabel::new(display).wrap()]; Container::new(elements).wrap() diff --git a/src/interface/elements/mutable_range.rs b/korangar/src/interface/elements/mutable_range.rs similarity index 64% rename from src/interface/elements/mutable_range.rs rename to korangar/src/interface/elements/mutable_range.rs index 6d8580c9..305ebfe5 100644 --- a/src/interface/elements/mutable_range.rs +++ b/korangar/src/interface/elements/mutable_range.rs @@ -1,12 +1,23 @@ use std::fmt::Display; +use korangar_interface::elements::{Container, ElementCell, ElementDisplay, ElementWrap, StaticLabel}; +use korangar_interface::event::ChangeEvent; use num::traits::NumOps; use num::{NumCast, Zero}; -use crate::interface::*; +use super::{MutableArrayValue, MutableNumberValue}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ArrayType, CornerRadius, ScreenPosition, ScreenSize}; +use crate::loaders::{FontSize, Scaling}; pub trait PrototypeMutableRangeElement { - fn to_mutable_range_element(&self, display: String, minimum: Self, maximum: Self, change_event: Option) -> ElementCell; + fn to_mutable_range_element( + &self, + display: String, + minimum: Self, + maximum: Self, + change_event: Option, + ) -> ElementCell; } // workaround for not having negative trait bounds or better specialization @@ -16,9 +27,17 @@ impl !IsVector for f32 {} impl IsVector for ScreenPosition {} impl IsVector for ScreenSize {} impl IsVector for CornerRadius {} +impl IsVector for FontSize {} +impl IsVector for Scaling {} impl PrototypeMutableRangeElement for f32 { - fn to_mutable_range_element(&self, display: String, minimum: Self, maximum: Self, change_event: Option) -> ElementCell { + fn to_mutable_range_element( + &self, + display: String, + minimum: Self, + maximum: Self, + change_event: Option, + ) -> ElementCell { // SAFETY: This is obviously unsafe, so one needs to make sure that the element // implementing `PrototypeMutableRangeElement` will be valid and pinned while // this element exists. Additionally, it should only be used in a debug @@ -40,7 +59,13 @@ where T::Element: Zero + NumOps + NumCast + Copy + PartialOrd + Display + 'static, [(); T::ELEMENT_COUNT]:, { - fn to_mutable_range_element(&self, display: String, minimum: Self, maximum: Self, change_event: Option) -> ElementCell { + fn to_mutable_range_element( + &self, + display: String, + minimum: Self, + maximum: Self, + change_event: Option, + ) -> ElementCell { // SAFETY: This is obviously unsafe, so one needs to make sure that the element // implementing `PrototypeMutableRangeElement` will be valid and pinned while // this element exists. Additionally, it should only be used in a debug diff --git a/src/interface/elements/profiler/colors.rs b/korangar/src/interface/elements/profiler/colors.rs similarity index 100% rename from src/interface/elements/profiler/colors.rs rename to korangar/src/interface/elements/profiler/colors.rs diff --git a/src/interface/elements/profiler/frame.rs b/korangar/src/interface/elements/profiler/frame.rs similarity index 69% rename from src/interface/elements/profiler/frame.rs rename to korangar/src/interface/elements/profiler/frame.rs index 7db3fd4a..417e86a2 100644 --- a/src/interface/elements/profiler/frame.rs +++ b/korangar/src/interface/elements/profiler/frame.rs @@ -1,19 +1,28 @@ -use procedural::size_bound; +use korangar_interface::application::FontSizeTrait; +use korangar_interface::elements::{Element, ElementState}; +use korangar_interface::event::{ChangeEvent, ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_interface::state::{PlainRemote, Remote}; +use korangar_procedural::size_bound; use crate::debug::*; use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::FrameInspectorWindow; +use crate::loaders::FontSize; pub struct FrameView { - state: ElementState, + state: ElementState, frame_counter: usize, - always_update: Remote, - visible_thread: Remote, + always_update: PlainRemote, + visible_thread: PlainRemote, } impl FrameView { - pub fn new(always_update: Remote, visible_thread: Remote) -> Self { + pub fn new(always_update: PlainRemote, visible_thread: PlainRemote) -> Self { Self { state: ElementState::default(), frame_counter: 0, @@ -23,12 +32,12 @@ impl FrameView { } } -impl Element for FrameView { - fn get_state(&self) -> &ElementState { +impl Element for FrameView { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } @@ -36,7 +45,12 @@ impl Element for FrameView { false } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, _theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + _theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, 300); self.state.resolve(placement_resolver, size_bound); } @@ -44,7 +58,7 @@ impl Element for FrameView { fn update(&mut self) -> Option { self.frame_counter += 1; - if *self.always_update.borrow() || self.frame_counter == SAVED_FRAME_COUNT { + if *self.always_update.get() || self.frame_counter == SAVED_FRAME_COUNT { self.frame_counter = 0; return Some(ChangeEvent::RENDER_WINDOW); } @@ -52,15 +66,15 @@ impl Element for FrameView { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position), _ => HoverInformation::Missed, } } - fn left_click(&mut self, _update: &mut bool) -> Vec { - let visible_thread = *self.visible_thread.borrow(); + fn left_click(&mut self, _update: &mut bool) -> Vec> { + let visible_thread = *self.visible_thread.get(); let mouse_position = self.state.mouse_position.get(); let number_of_frames = get_number_of_saved_frames(visible_thread); @@ -75,21 +89,20 @@ impl Element for FrameView { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, _theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + _hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); - let (entries, statistics_map, longest_frame) = get_statistics_data(*self.visible_thread.borrow()); + let (entries, statistics_map, longest_frame) = get_statistics_data(*self.visible_thread.get()); let bar_width = (self.state.cached_size.width - 50.0) / entries.len() as f32; let gap_width = 50.0 / entries.len() as f32; @@ -144,9 +157,9 @@ impl Element for FrameView { let shadow_position = text_position + ScreenSize::uniform(1.0); // Drop shadow. - renderer.render_text(&text, shadow_position, Color::monochrome_u8(0), 14.0); + renderer.render_text(&text, shadow_position, Color::monochrome_u8(0), FontSize::new(14.0)); // Colored text. - renderer.render_text(&text, text_position, color, 14.0); + renderer.render_text(&text, text_position, color, FontSize::new(14.0)); y_position += 14.0; } diff --git a/src/interface/elements/profiler/inspector.rs b/korangar/src/interface/elements/profiler/inspector.rs similarity index 77% rename from src/interface/elements/profiler/inspector.rs rename to korangar/src/interface/elements/profiler/inspector.rs index a055e23e..16e77ff4 100644 --- a/src/interface/elements/profiler/inspector.rs +++ b/korangar/src/interface/elements/profiler/inspector.rs @@ -1,18 +1,26 @@ use std::time::{Duration, Instant}; -use procedural::size_bound; +use derive_new::new; +use korangar_interface::application::FontSizeTrait; +use korangar_interface::elements::{Element, ElementRenderer, ElementState}; +use korangar_interface::event::{ChangeEvent, HoverInformation}; +use korangar_interface::layout::PlacementResolver; +use korangar_procedural::size_bound; use crate::debug::*; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{CornerRadius, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::theme::InterfaceTheme; +use crate::FontSize; const VISIBILITY_THRESHHOLD: f32 = 0.01; #[derive(new)] pub struct FrameInspectorView { #[new(default)] - state: ElementState, + state: ElementState, measurement: Measurement, #[new(default)] start_offset: Duration, @@ -38,7 +46,7 @@ impl FrameInspectorView { } fn render_lines( - renderer: &mut ElementRenderer<'_>, + renderer: &mut ElementRenderer<'_, InterfaceSettings>, theme: &InterfaceTheme, text: &str, text_width: f32, @@ -56,7 +64,7 @@ impl FrameInspectorView { top: 0.0, }; let line_size = ScreenSize { - width: theme.profiler.line_width.get() * renderer.interface_settings.scaling.get(), + width: theme.profiler.line_width.get() * renderer.application.get_scaling_factor(), height: size.height, }; @@ -65,16 +73,17 @@ impl FrameInspectorView { if render_numbers { let offset = ScreenPosition { left: x_position + (distance - text_width) / 2.0, - top: size.height - theme.profiler.distance_text_offset.get() * renderer.interface_settings.scaling.get(), + top: size.height - theme.profiler.distance_text_offset.get() * renderer.application.get_scaling_factor(), }; - renderer.renderer.render_text( + korangar_interface::application::InterfaceRenderer::render_text( + renderer.renderer, renderer.render_target, text, renderer.position + offset, - renderer.screen_clip, + renderer.clip, theme.profiler.line_color.get(), - theme.profiler.distance_text_size.get() * renderer.interface_settings.scaling.get(), + FontSize::new(theme.profiler.distance_text_size.get() * renderer.application.get_scaling_factor()), ); } @@ -83,7 +92,7 @@ impl FrameInspectorView { } fn render_measurement( - renderer: &mut ElementRenderer<'_>, + renderer: &mut ElementRenderer<'_, InterfaceSettings>, color_lookup: &mut super::ColorLookup, theme: &InterfaceTheme, measurement: &Measurement, @@ -97,15 +106,15 @@ impl FrameInspectorView { // Size in scaled pixels at which the text starts fading in const TEXT_DISPLAY_SIZE: f32 = 50.0; - let scaled_bar_gap = theme.profiler.bar_gap.get().width * renderer.interface_settings.scaling.get(); + let scaled_bar_gap = theme.profiler.bar_gap.get().width * renderer.application.get_scaling_factor(); let color = color_lookup.get_color(measurement.name); - let text_offset = theme.profiler.bar_text_offset.get() * renderer.interface_settings.scaling.get(); + let text_offset = theme.profiler.bar_text_offset.get() * renderer.application.get_scaling_factor(); let x_position = measurement.start_time.saturating_duration_since(start_time).as_secs_f32() * unit + scaled_bar_gap; let x_size = measurement.end_time.saturating_duration_since(start_time).as_secs_f32() * unit - x_position - scaled_bar_gap; let x_size = x_size.min(total_width - x_position - scaled_bar_gap); - let y_size = theme.profiler.bar_height.get() * renderer.interface_settings.scaling.get(); + let y_size = theme.profiler.bar_height.get() * renderer.application.get_scaling_factor(); - let alpha = Self::interpolate_alpha_linear(BAR_FADE_SPEED * renderer.interface_settings.scaling.get(), 0.0, x_size); + let alpha = Self::interpolate_alpha_linear(BAR_FADE_SPEED * renderer.application.get_scaling_factor(), 0.0, x_size); if alpha < VISIBILITY_THRESHHOLD { return; } @@ -127,18 +136,18 @@ impl FrameInspectorView { ); let alpha = Self::interpolate_alpha_linear( - TEXT_FADE_SPEED * renderer.interface_settings.scaling.get(), - TEXT_DISPLAY_SIZE * renderer.interface_settings.scaling.get(), + TEXT_FADE_SPEED * renderer.application.get_scaling_factor(), + TEXT_DISPLAY_SIZE * renderer.application.get_scaling_factor(), x_size, ); if alpha > VISIBILITY_THRESHHOLD { let text = format!("{} ({:?})", measurement.name, measurement.end_time - measurement.start_time); let screen_clip = ScreenClip { - left: renderer.screen_clip.left + x_position, - top: renderer.screen_clip.top + y_position, - right: renderer.screen_clip.left + x_position + x_size, - bottom: renderer.screen_clip.top + y_position + y_size, + left: renderer.clip.left + x_position, + top: renderer.clip.top + y_position, + right: renderer.clip.left + x_position + x_size, + bottom: renderer.clip.top + y_position + y_size, }; let text_position = renderer.position @@ -148,18 +157,19 @@ impl FrameInspectorView { } + text_offset; - renderer.renderer.render_text( + korangar_interface::application::InterfaceRenderer::render_text( + renderer.renderer, renderer.render_target, &text, text_position, screen_clip, theme.profiler.bar_text_color.get().multiply_alpha(alpha), - theme.profiler.bar_text_size.get() * renderer.interface_settings.scaling.get(), + FontSize::new(theme.profiler.bar_text_size.get() * renderer.application.get_scaling_factor()), ); } let y_position = y_position - + (theme.profiler.bar_gap.get().height + theme.profiler.bar_height.get()) * renderer.interface_settings.scaling.get(); + + (theme.profiler.bar_gap.get().height + theme.profiler.bar_height.get()) * renderer.application.get_scaling_factor(); measurement.indices.iter().for_each(|measurement| { Self::render_measurement( @@ -176,12 +186,12 @@ impl FrameInspectorView { } } -impl Element for FrameInspectorView { - fn get_state(&self) -> &ElementState { +impl Element for FrameInspectorView { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } @@ -189,12 +199,17 @@ impl Element for FrameInspectorView { false } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, _theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + _theme: &InterfaceTheme, + ) { let size_bound = &size_bound!(100%, 300); self.state.resolve(placement_resolver, size_bound); } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position), _ => HoverInformation::Missed, @@ -223,13 +238,12 @@ impl Element for FrameInspectorView { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + _hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { @@ -239,7 +253,7 @@ impl Element for FrameInspectorView { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); renderer.render_background(theme.profiler.corner_radius.get(), theme.profiler.background_color.get()); @@ -265,8 +279,8 @@ impl Element for FrameInspectorView { let offset = ((self.start_offset.$div_function() as f32 / $divider) * -distance) % distance; let text_width = renderer - .get_text_dimensions($text, theme.profiler.distance_text_size.get(), f32::MAX) - .x; + .get_text_dimensions($text, FontSize::new(theme.profiler.distance_text_size.get()), f32::MAX) + .width; let show_numbers = !numbers_shown && distance > text_width * DISTANCE_NUMBER_SIZE; #[allow(unused_assignments)] @@ -306,7 +320,7 @@ impl Element for FrameInspectorView { start_time, self.state.cached_size.width, self.state.cached_size.width / viewed_duration.as_secs_f32(), - theme.profiler.bar_gap.get().height * interface_settings.scaling.get(), + theme.profiler.bar_gap.get().height * application.get_scaling_factor(), ); } } diff --git a/src/interface/elements/profiler/mod.rs b/korangar/src/interface/elements/profiler/mod.rs similarity index 100% rename from src/interface/elements/profiler/mod.rs rename to korangar/src/interface/elements/profiler/mod.rs diff --git a/src/interface/elements/values/mutable/array.rs b/korangar/src/interface/elements/values/array.rs similarity index 73% rename from src/interface/elements/values/mutable/array.rs rename to korangar/src/interface/elements/values/array.rs index 64fca498..abbaf03e 100644 --- a/src/interface/elements/values/mutable/array.rs +++ b/korangar/src/interface/elements/values/array.rs @@ -1,12 +1,18 @@ use std::cmp::PartialOrd; use std::fmt::Display; +use korangar_interface::elements::{Element, ElementDisplay, ElementState}; +use korangar_interface::event::{ChangeEvent, ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; use num::traits::NumOps; use num::{NumCast, Zero}; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::{Element, *}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ArrayType, ScreenClip, ScreenPosition}; +use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::ArrayWindow; pub struct MutableArrayValue where @@ -21,7 +27,7 @@ where change_event: Option, cached_inner: T, cached_values: String, - state: ElementState, + state: ElementState, } impl MutableArrayValue @@ -48,21 +54,26 @@ where } } -impl Element for MutableArrayValue +impl Element for MutableArrayValue where T: ArrayType + ElementDisplay + Copy + PartialEq + 'static, T::Element: Zero + NumOps + NumCast + Copy + PartialOrd + Display + 'static, [(); T::ELEMENT_COUNT]:, { - fn get_state(&self) -> &ElementState { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { self.state.resolve(placement_resolver, &theme.value.size_bound); } @@ -78,14 +89,14 @@ where None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position), _ => HoverInformation::Missed, } } - fn left_click(&mut self, _force_update: &mut bool) -> Vec { + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { let prototype_window = ArrayWindow::new( self.name.clone(), self.reference, @@ -101,19 +112,18 @@ where &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let background_color = match self.is_element_self(hovered_element) { true => theme.value.hovered_background_color.get(), diff --git a/src/interface/elements/values/mutable/color.rs b/korangar/src/interface/elements/values/color.rs similarity index 70% rename from src/interface/elements/values/mutable/color.rs rename to korangar/src/interface/elements/values/color.rs index 544ff2bc..5fc086e4 100644 --- a/src/interface/elements/values/mutable/color.rs +++ b/korangar/src/interface/elements/values/color.rs @@ -1,6 +1,13 @@ +use korangar_interface::elements::{Element, ElementState}; +use korangar_interface::event::{ChangeEvent, ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; + use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::{ColorWindow, Element, *}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition}; +use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::ColorWindow; pub struct MutableColorValue { name: String, @@ -8,7 +15,7 @@ pub struct MutableColorValue { change_event: Option, cached_color: Color, cached_values: String, - state: ElementState, + state: ElementState, } impl MutableColorValue { @@ -34,16 +41,21 @@ impl MutableColorValue { } } -impl Element for MutableColorValue { - fn get_state(&self) -> &ElementState { +impl Element for MutableColorValue { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { self.state.resolve(placement_resolver, &theme.value.size_bound); } @@ -65,14 +77,14 @@ impl Element for MutableColorValue { None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position), _ => HoverInformation::Missed, } } - fn left_click(&mut self, _force_update: &mut bool) -> Vec { + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { vec![ClickAction::OpenWindow(Box::new(ColorWindow::new( self.name.clone(), self.reference, @@ -84,19 +96,18 @@ impl Element for MutableColorValue { &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let background_color = match self.is_element_self(hovered_element) { true => self.cached_color.shade(), diff --git a/src/interface/elements/values/mutable/mod.rs b/korangar/src/interface/elements/values/mod.rs similarity index 100% rename from src/interface/elements/values/mutable/mod.rs rename to korangar/src/interface/elements/values/mod.rs diff --git a/src/interface/elements/values/mutable/number.rs b/korangar/src/interface/elements/values/number.rs similarity index 71% rename from src/interface/elements/values/mutable/number.rs rename to korangar/src/interface/elements/values/number.rs index 03862027..6ca1e1ab 100644 --- a/src/interface/elements/values/mutable/number.rs +++ b/korangar/src/interface/elements/values/number.rs @@ -1,12 +1,18 @@ use std::cmp::PartialOrd; use std::fmt::Display; +use korangar_interface::elements::{Element, ElementState}; +use korangar_interface::event::{ChangeEvent, ClickAction, HoverInformation}; +use korangar_interface::layout::PlacementResolver; use num::traits::NumOps; use num::{NumCast, Zero}; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; -use crate::interface::{Element, NumberWindow, *}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition}; +use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::NumberWindow; pub struct MutableNumberValue { name: String, @@ -16,7 +22,7 @@ pub struct MutableNumberValue, cached_inner: T, cached_values: String, - state: ElementState, + state: ElementState, } impl MutableNumberValue { @@ -38,16 +44,21 @@ impl Mutable } } -impl Element for MutableNumberValue { - fn get_state(&self) -> &ElementState { +impl Element for MutableNumberValue { + fn get_state(&self) -> &ElementState { &self.state } - fn get_state_mut(&mut self) -> &mut ElementState { + fn get_state_mut(&mut self) -> &mut ElementState { &mut self.state } - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { + fn resolve( + &mut self, + placement_resolver: &mut PlacementResolver, + _application: &InterfaceSettings, + theme: &InterfaceTheme, + ) { self.state.resolve(placement_resolver, &theme.value.size_bound); } @@ -63,14 +74,14 @@ impl Element None } - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { + fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { match mouse_mode { MouseInputMode::None => self.state.hovered_element(mouse_position), _ => HoverInformation::Missed, } } - fn left_click(&mut self, _force_update: &mut bool) -> Vec { + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { vec![ClickAction::OpenWindow(Box::new(NumberWindow::new( self.name.clone(), self.reference, @@ -84,19 +95,18 @@ impl Element &self, render_target: &mut ::Target, renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, theme: &InterfaceTheme, parent_position: ScreenPosition, screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, + hovered_element: Option<&dyn Element>, + _focused_element: Option<&dyn Element>, _mouse_mode: &MouseInputMode, _second_theme: bool, ) { let mut renderer = self .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); + .element_renderer(render_target, renderer, application, parent_position, screen_clip); let background_color = match self.is_element_self(hovered_element) { true => theme.value.hovered_background_color.get(), diff --git a/src/interface/elements/wrappers/mod.rs b/korangar/src/interface/elements/wrappers/mod.rs similarity index 100% rename from src/interface/elements/wrappers/mod.rs rename to korangar/src/interface/elements/wrappers/mod.rs diff --git a/src/interface/elements/wrappers/mutable.rs b/korangar/src/interface/elements/wrappers/mutable.rs similarity index 65% rename from src/interface/elements/wrappers/mutable.rs rename to korangar/src/interface/elements/wrappers/mutable.rs index 4781dcee..ad60ef88 100644 --- a/src/interface/elements/wrappers/mutable.rs +++ b/korangar/src/interface/elements/wrappers/mutable.rs @@ -1,9 +1,12 @@ use std::marker::PhantomData; use derive_new::new; +use korangar_interface::elements::{ElementCell, PrototypeElement}; +use korangar_interface::event::{ChangeEvent, IntoChangeEvent}; use serde::{Deserialize, Serialize}; -use crate::interface::{ChangeEvent, ElementCell, IntoChangeEvent, PrototypeElement, PrototypeMutableElement}; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::PrototypeMutableElement; #[derive(Serialize, Deserialize, new)] pub struct Mutable @@ -13,6 +16,7 @@ where { data: T, #[new(default)] + #[serde(skip)] _phantom_data: PhantomData, } @@ -26,12 +30,12 @@ where } } -impl PrototypeElement for Mutable +impl PrototypeElement for Mutable where T: Copy + PrototypeMutableElement, E: IntoChangeEvent, { - fn to_element(&self, display: String) -> ElementCell { + fn to_element(&self, display: String) -> ElementCell { self.data.to_mutable_element(display, E::into_change_event()) } } @@ -41,7 +45,7 @@ where T: Copy + PrototypeMutableElement, E: IntoChangeEvent, { - fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { + fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { self.data.to_mutable_element(display, E::into_change_event()) } } diff --git a/src/interface/elements/wrappers/mutable_range.rs b/korangar/src/interface/elements/wrappers/mutable_range.rs similarity index 70% rename from src/interface/elements/wrappers/mutable_range.rs rename to korangar/src/interface/elements/wrappers/mutable_range.rs index 2dad6e78..12f4a96a 100644 --- a/src/interface/elements/wrappers/mutable_range.rs +++ b/korangar/src/interface/elements/wrappers/mutable_range.rs @@ -1,11 +1,12 @@ use std::marker::PhantomData; use derive_new::new; +use korangar_interface::elements::{ElementCell, PrototypeElement}; +use korangar_interface::event::{ChangeEvent, IntoChangeEvent}; use serde::{Deserialize, Serialize}; -use crate::interface::{ - ChangeEvent, ElementCell, IntoChangeEvent, PrototypeElement, PrototypeMutableElement, PrototypeMutableRangeElement, -}; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::{PrototypeMutableElement, PrototypeMutableRangeElement}; // TODO: rework when const generics are able to do this: //pub struct MutableRange, const MAX: Vector2>(pub @@ -21,6 +22,7 @@ where minimum: T, maximum: T, #[new(default)] + #[serde(skip)] _phantom_data: PhantomData, } @@ -34,12 +36,12 @@ where } } -impl PrototypeElement for MutableRange +impl PrototypeElement for MutableRange where T: Copy + PrototypeMutableRangeElement, E: IntoChangeEvent, { - fn to_element(&self, display: String) -> ElementCell { + fn to_element(&self, display: String) -> ElementCell { self.inner .to_mutable_range_element(display, self.minimum, self.maximum, E::into_change_event()) } @@ -50,7 +52,7 @@ where T: Copy + PrototypeMutableRangeElement, E: IntoChangeEvent, { - fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { + fn to_mutable_element(&self, display: String, _change_event: Option) -> ElementCell { self.inner .to_mutable_range_element(display, self.minimum, self.maximum, E::into_change_event()) } diff --git a/src/interface/layout/size.rs b/korangar/src/interface/layout.rs similarity index 99% rename from src/interface/layout/size.rs rename to korangar/src/interface/layout.rs index 50eed0df..45a8cb06 100644 --- a/src/interface/layout/size.rs +++ b/korangar/src/interface/layout.rs @@ -1,8 +1,7 @@ use derive_new::new; +use korangar_interface::elements::ElementDisplay; use serde::{Deserialize, Serialize}; -use crate::interface::ElementDisplay; - pub trait ArrayType { type Element; diff --git a/korangar/src/interface/mod.rs b/korangar/src/interface/mod.rs new file mode 100644 index 00000000..67a7416e --- /dev/null +++ b/korangar/src/interface/mod.rs @@ -0,0 +1,9 @@ +pub mod layout; +pub mod theme; +#[macro_use] +pub mod elements; +pub mod application; +pub mod cursor; +pub mod dialog; +pub mod resource; +pub mod windows; diff --git a/korangar/src/interface/resource.rs b/korangar/src/interface/resource.rs new file mode 100644 index 00000000..3806256f --- /dev/null +++ b/korangar/src/interface/resource.rs @@ -0,0 +1,49 @@ +use crate::input::HotbarSlot; +use crate::inventory::{Item, Skill}; +use crate::network::EquipPosition; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ItemSource { + Inventory, + Equipment { position: EquipPosition }, +} + +#[derive(Debug, Clone)] +pub struct ItemMove { + pub source: ItemSource, + pub destination: ItemSource, + pub item: Item, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SkillSource { + SkillTree, + Hotbar { slot: HotbarSlot }, +} + +#[derive(Clone, Debug)] +pub struct SkillMove { + pub source: SkillSource, + pub destination: SkillSource, + pub skill: Skill, +} + +#[derive(Clone, Debug)] +pub enum PartialMove { + Item { source: ItemSource, item: Item }, + Skill { source: SkillSource, skill: Skill }, +} + +#[derive(Clone, Debug)] +pub enum Move { + Item { + source: ItemSource, + destination: ItemSource, + item: Item, + }, + Skill { + source: SkillSource, + destination: SkillSource, + skill: Skill, + }, +} diff --git a/korangar/src/interface/theme/actions.rs b/korangar/src/interface/theme/actions.rs new file mode 100644 index 00000000..a443dd2b --- /dev/null +++ b/korangar/src/interface/theme/actions.rs @@ -0,0 +1,42 @@ +use korangar_interface::elements::{ButtonBuilder, Container, ElementCell, ElementWrap, Headline, PrototypeElement}; +use korangar_procedural::{dimension_bound, size_bound, PrototypeElement}; + +use crate::input::UserEvent; +use crate::interface::application::{InterfaceSettings, InternalThemeKind}; + +/// Debug actions for a single theme. +#[derive(Default)] +struct Actions; + +impl PrototypeElement for Actions { + fn to_element(&self, display: String) -> ElementCell { + let elements = vec![ + Headline::new(display, size_bound!(33%, 12)).wrap(), + ButtonBuilder::new() + .with_text("Save") + .with_event(UserEvent::SaveTheme { theme_kind: KIND }) + .with_width_bound(dimension_bound!(33%)) + .build() + .wrap(), + ButtonBuilder::new() + .with_text("Reload") + .with_event(UserEvent::ReloadTheme { theme_kind: KIND }) + .with_width_bound(dimension_bound!(!)) + .build() + .wrap(), + ]; + + Container::new(elements).wrap() + } +} + +/// Debug actions for all themes. +#[derive(Default, PrototypeElement)] +pub(super) struct ThemeActions { + #[name("Main theme")] + main_theme_actions: Actions<{ InternalThemeKind::Main }>, + #[name("Menu theme")] + menu_theme_actions: Actions<{ InternalThemeKind::Menu }>, + #[name("Game theme")] + game_theme_actions: Actions<{ InternalThemeKind::Game }>, +} diff --git a/src/interface/theme.rs b/korangar/src/interface/theme/mod.rs similarity index 67% rename from src/interface/theme.rs rename to korangar/src/interface/theme/mod.rs index 71b163d3..9613b70a 100644 --- a/src/interface/theme.rs +++ b/korangar/src/interface/theme/mod.rs @@ -1,25 +1,48 @@ -use procedural::{dimension_bound, size_bound, PrototypeElement, PrototypeWindow}; +use korangar_interface::application::FontSizeTrait; +use korangar_interface::event::{Nothing, Render, Resolve}; +use korangar_interface::layout::{DimensionBound, SizeBound}; +use korangar_procedural::{dimension_bound, size_bound, PrototypeElement, PrototypeWindow}; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; +#[cfg(feature = "debug")] +mod actions; + +#[cfg(feature = "debug")] +use self::actions::ThemeActions; +use super::application::InterfaceSettings; +use super::elements::{Mutable, MutableRange}; +use super::layout::{CornerRadius, ScreenPosition, ScreenSize}; #[cfg(feature = "debug")] use crate::debug::*; use crate::graphics::Color; -use crate::interface::state::TrackedStateTake; -use crate::interface::*; +use crate::loaders::FontSize; + +/// Themes the user interface can use. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum InterfaceThemeKind { + Menu, + #[default] + Main, +} -pub struct Menu; -pub struct Main; +/// Marker trait to specialize the [`ThemeDefault`] trait. +pub trait ThemeKindMarker {} -pub trait ThemeType {} +/// Default theme in the menu. +pub struct DefaultMenu; +impl ThemeKindMarker for DefaultMenu {} -impl ThemeType for Menu {} -impl ThemeType for Main {} +/// Default theme when in game. +pub struct DefaultMain; +impl ThemeKindMarker for DefaultMain {} -pub trait ThemeDefault { +/// Default trait that can be specialized over a theme type. +pub trait ThemeDefault { fn default() -> Self; } +// TODO: Make all theme fileds private. Use the traits to access fields #[derive(Serialize, Deserialize, PrototypeElement)] pub struct ButtonTheme { pub background_color: Mutable, @@ -34,11 +57,11 @@ pub struct ButtonTheme { pub icon_size: MutableRange, pub icon_text_offset: MutableRange, pub text_offset: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, pub height_bound: DimensionBound, } -impl ThemeDefault for ButtonTheme { +impl ThemeDefault for ButtonTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(150, 70, 255)), @@ -69,13 +92,13 @@ impl ThemeDefault for ButtonTheme { ScreenPosition::default(), ScreenPosition { left: 100.0, top: 20.0 }, ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), height_bound: dimension_bound!(26), } } } -impl ThemeDefault
for ButtonTheme { +impl ThemeDefault for ButtonTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(100)), @@ -102,12 +125,70 @@ impl ThemeDefault
for ButtonTheme { ScreenPosition::default(), ScreenPosition { left: 100.0, top: 20.0 }, ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), height_bound: dimension_bound!(16), } } } +impl korangar_interface::theme::ButtonTheme for ButtonTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn hovered_background_color(&self) -> Color { + self.hovered_background_color.get() + } + + fn disabled_background_color(&self) -> Color { + self.disabled_background_color.get() + } + + fn foreground_color(&self) -> Color { + self.foreground_color.get() + } + + fn hovered_foreground_color(&self) -> Color { + self.hovered_foreground_color.get() + } + + fn disabled_foreground_color(&self) -> Color { + self.disabled_foreground_color.get() + } + + fn debug_foreground_color(&self) -> Color { + self.debug_foreground_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn icon_offset(&self) -> ScreenPosition { + self.icon_offset.get() + } + + fn icon_size(&self) -> ScreenSize { + self.icon_size.get() + } + + fn icon_text_offset(&self) -> ScreenPosition { + self.icon_text_offset.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } + + fn height_bound(&self) -> korangar_interface::layout::DimensionBound { + self.height_bound.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct WindowTheme { pub background_color: Mutable, @@ -118,11 +199,11 @@ pub struct WindowTheme { pub border_size: MutableRange, pub text_offset: MutableRange, pub gaps: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, pub title_height: DimensionBound, } -impl ThemeDefault for WindowTheme { +impl ThemeDefault for WindowTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(30)), @@ -145,13 +226,13 @@ impl ThemeDefault for WindowTheme { ScreenSize::default(), ScreenSize::uniform(20.0), ), - font_size: MutableRange::new(20.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(20.0), FontSize::new(6.0), FontSize::new(30.0)), title_height: dimension_bound!(30), } } } -impl ThemeDefault
for WindowTheme { +impl ThemeDefault for WindowTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(40)), @@ -174,12 +255,54 @@ impl ThemeDefault
for WindowTheme { ScreenSize::default(), ScreenSize::uniform(20.0), ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), title_height: dimension_bound!(12), } } } +impl korangar_interface::theme::WindowTheme for WindowTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn title_background_color(&self) -> Color { + self.title_background_color.get() + } + + fn foreground_color(&self) -> Color { + self.foreground_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn title_corner_radius(&self) -> CornerRadius { + self.title_corner_radius.get() + } + + fn border_size(&self) -> ScreenSize { + self.border_size.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn gaps(&self) -> ScreenSize { + self.gaps.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } + + fn title_height(&self) -> korangar_interface::layout::DimensionBound { + self.title_height.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct ExpandableTheme { pub background_color: Mutable, @@ -193,10 +316,10 @@ pub struct ExpandableTheme { pub icon_size: MutableRange, pub text_offset: MutableRange, pub gaps: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, } -impl ThemeDefault for ExpandableTheme { +impl ThemeDefault for ExpandableTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(60)), @@ -222,12 +345,12 @@ impl ThemeDefault for ExpandableTheme { ScreenPosition { left: 50.0, top: 20.0 }, ), gaps: MutableRange::new(ScreenSize::uniform(6.0), ScreenSize::default(), ScreenSize::uniform(20.0)), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), } } } -impl ThemeDefault
for ExpandableTheme { +impl ThemeDefault for ExpandableTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(60)), @@ -253,22 +376,72 @@ impl ThemeDefault
for ExpandableTheme { ScreenPosition { left: 50.0, top: 20.0 }, ), gaps: MutableRange::new(ScreenSize::uniform(6.0), ScreenSize::default(), ScreenSize::uniform(20.0)), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), } } } +impl korangar_interface::theme::ExpandableTheme for ExpandableTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn second_background_color(&self) -> Color { + self.second_background_color.get() + } + + fn foreground_color(&self) -> Color { + self.foreground_color.get() + } + + fn hovered_foreground_color(&self) -> Color { + self.hovered_foreground_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn border_size(&self) -> ScreenSize { + self.border_size.get() + } + + fn element_offset(&self) -> ScreenPosition { + self.element_offset.get() + } + + fn icon_offset(&self) -> ScreenPosition { + self.icon_offset.get() + } + + fn icon_size(&self) -> ScreenSize { + self.icon_size.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn gaps(&self) -> ScreenSize { + self.gaps.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct LabelTheme { pub background_color: Mutable, pub foreground_color: Mutable, pub corner_radius: MutableRange, pub text_offset: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, pub size_bound: SizeBound, } -impl ThemeDefault for LabelTheme { +impl ThemeDefault for LabelTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(130)), @@ -279,13 +452,13 @@ impl ThemeDefault for LabelTheme { ScreenPosition::uniform(-10.0), ScreenPosition::uniform(20.0), ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), size_bound: size_bound!(120 > 50% < 300, 0), } } } -impl ThemeDefault
for LabelTheme { +impl ThemeDefault for LabelTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(130)), @@ -296,12 +469,38 @@ impl ThemeDefault
for LabelTheme { ScreenPosition::uniform(-10.0), ScreenPosition::uniform(20.0), ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), size_bound: size_bound!(120 > 50% < 300, 0), } } } +impl korangar_interface::theme::LabelTheme for LabelTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn foreground_color(&self) -> Color { + self.foreground_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } + + fn size_bound(&self) -> korangar_interface::layout::SizeBound { + self.size_bound.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct ValueTheme { pub background_color: Mutable, @@ -309,11 +508,11 @@ pub struct ValueTheme { pub foreground_color: Mutable, pub corner_radius: MutableRange, pub text_offset: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, pub size_bound: SizeBound, } -impl ThemeDefault for ValueTheme { +impl ThemeDefault for ValueTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(100, 100, 100)), @@ -325,13 +524,13 @@ impl ThemeDefault for ValueTheme { ScreenPosition::uniform(-10.0), ScreenPosition::uniform(20.0), ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), size_bound: size_bound!(60 > !, 14), } } } -impl ThemeDefault
for ValueTheme { +impl ThemeDefault for ValueTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(100, 100, 100)), @@ -343,12 +542,42 @@ impl ThemeDefault
for ValueTheme { ScreenPosition::uniform(-10.0), ScreenPosition::uniform(20.0), ), - font_size: MutableRange::new(14.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(30.0)), size_bound: size_bound!(60 > !, 14), } } } +impl korangar_interface::theme::ValueTheme for ValueTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn hovered_background_color(&self) -> Color { + self.hovered_background_color.get() + } + + fn foreground_color(&self) -> Color { + self.foreground_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } + + fn size_bound(&self) -> korangar_interface::layout::SizeBound { + self.size_bound.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct CloseButtonTheme { pub background_color: Mutable, @@ -356,11 +585,11 @@ pub struct CloseButtonTheme { pub foreground_color: Mutable, pub corner_radius: MutableRange, pub text_offset: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, pub size_bound: SizeBound, } -impl ThemeDefault for CloseButtonTheme { +impl ThemeDefault for CloseButtonTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(200, 100, 100)), @@ -376,13 +605,13 @@ impl ThemeDefault for CloseButtonTheme { ScreenPosition::uniform(-10.0), ScreenPosition::uniform(20.0), ), - font_size: MutableRange::new(20.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(20.0), FontSize::new(6.0), FontSize::new(30.0)), size_bound: size_bound!(26, 26), } } } -impl ThemeDefault
for CloseButtonTheme { +impl ThemeDefault for CloseButtonTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(200, 100, 100)), @@ -394,17 +623,47 @@ impl ThemeDefault
for CloseButtonTheme { ScreenPosition::uniform(-10.0), ScreenPosition::uniform(20.0), ), - font_size: MutableRange::new(12.0, 6.0, 30.0), + font_size: MutableRange::new(FontSize::new(12.0), FontSize::new(6.0), FontSize::new(30.0)), size_bound: size_bound!(25, 12), } } } +impl korangar_interface::theme::CloseButtonTheme for CloseButtonTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn hovered_background_color(&self) -> Color { + self.hovered_background_color.get() + } + + fn foreground_color(&self) -> Color { + self.foreground_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } + + fn size_bound(&self) -> korangar_interface::layout::SizeBound { + self.size_bound.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct OverlayTheme { pub foreground_color: Mutable, pub text_offset: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, } impl Default for OverlayTheme { @@ -416,7 +675,7 @@ impl Default for OverlayTheme { ScreenPosition::default(), ScreenPosition { left: 1000.0, top: 500.0 }, ), - font_size: MutableRange::new(18.0, 6.0, 50.0), + font_size: MutableRange::new(FontSize::new(18.0), FontSize::new(6.0), FontSize::new(50.0)), } } } @@ -429,7 +688,7 @@ pub struct SliderTheme { pub size_bound: SizeBound, } -impl ThemeDefault for SliderTheme { +impl ThemeDefault for SliderTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(140, 80, 100)), @@ -440,7 +699,7 @@ impl ThemeDefault for SliderTheme { } } -impl ThemeDefault
for SliderTheme { +impl ThemeDefault for SliderTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgb_u8(140, 80, 100)), @@ -451,6 +710,24 @@ impl ThemeDefault
for SliderTheme { } } +impl korangar_interface::theme::SliderTheme for SliderTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn rail_color(&self) -> Color { + self.rail_color.get() + } + + fn knob_color(&self) -> Color { + self.knob_color.get() + } + + fn size_bound(&self) -> korangar_interface::layout::SizeBound { + self.size_bound.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct InputTheme { pub background_color: Mutable, @@ -460,14 +737,14 @@ pub struct InputTheme { pub ghost_text_color: Mutable, pub focused_text_color: Mutable, pub corner_radius: MutableRange, - pub font_size: MutableRange, + pub font_size: MutableRange, pub text_offset: MutableRange, pub cursor_offset: MutableRange, pub cursor_width: MutableRange, pub height_bound: DimensionBound, } -impl ThemeDefault for InputTheme { +impl ThemeDefault for InputTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(45)), @@ -481,7 +758,7 @@ impl ThemeDefault for InputTheme { CornerRadius::default(), CornerRadius::uniform(30.0), ), - font_size: MutableRange::new(15.0, 6.0, 50.0), + font_size: MutableRange::new(FontSize::new(15.0), FontSize::new(6.0), FontSize::new(50.0)), text_offset: MutableRange::new( ScreenPosition { left: 15.0, top: 6.0 }, ScreenPosition::default(), @@ -494,7 +771,7 @@ impl ThemeDefault for InputTheme { } } -impl ThemeDefault
for InputTheme { +impl ThemeDefault for InputTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(60)), @@ -504,7 +781,7 @@ impl ThemeDefault
for InputTheme { ghost_text_color: Mutable::new(Color::monochrome_u8(100)), focused_text_color: Mutable::new(Color::monochrome_u8(200)), corner_radius: MutableRange::new(CornerRadius::uniform(6.0), CornerRadius::default(), CornerRadius::uniform(30.0)), - font_size: MutableRange::new(14.0, 6.0, 50.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(50.0)), text_offset: MutableRange::new( ScreenPosition { left: 4.0, top: 0.0 }, ScreenPosition::default(), @@ -517,30 +794,90 @@ impl ThemeDefault
for InputTheme { } } +impl korangar_interface::theme::InputTheme for InputTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn hovered_background_color(&self) -> Color { + self.hovered_background_color.get() + } + + fn focused_background_color(&self) -> Color { + self.focused_background_color.get() + } + + fn text_color(&self) -> Color { + self.text_color.get() + } + + fn ghost_text_color(&self) -> Color { + self.ghost_text_color.get() + } + + fn focused_text_color(&self) -> Color { + self.focused_text_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } + + fn text_offset(&self) -> ScreenPosition { + self.text_offset.get() + } + + fn cursor_offset(&self) -> f32 { + self.cursor_offset.get() + } + + fn cursor_width(&self) -> f32 { + self.cursor_width.get() + } + + fn height_bound(&self) -> korangar_interface::layout::DimensionBound { + self.height_bound.clone() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct ChatTheme { pub background_color: Mutable, - pub font_size: MutableRange, + pub font_size: MutableRange, } -impl ThemeDefault for ChatTheme { +impl ThemeDefault for ChatTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgba_u8(0, 0, 0, 170)), - font_size: MutableRange::new(14.0, 6.0, 50.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(50.0)), } } } -impl ThemeDefault
for ChatTheme { +impl ThemeDefault for ChatTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::rgba_u8(0, 0, 0, 170)), - font_size: MutableRange::new(14.0, 6.0, 50.0), + font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(50.0)), } } } +impl korangar_interface::theme::ChatTheme for ChatTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn font_size(&self) -> FontSize { + self.font_size.get() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct CursorTheme { pub color: Mutable, @@ -570,7 +907,7 @@ pub struct ProfilerTheme { pub distance_text_offset: MutableRange, } -impl ThemeDefault for ProfilerTheme { +impl ThemeDefault for ProfilerTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(55)), @@ -596,7 +933,7 @@ impl ThemeDefault for ProfilerTheme { } } -impl ThemeDefault
for ProfilerTheme { +impl ThemeDefault for ProfilerTheme { fn default() -> Self { Self { background_color: Mutable::new(Color::monochrome_u8(55)), @@ -622,6 +959,56 @@ impl ThemeDefault
for ProfilerTheme { } } +impl korangar_interface::theme::ProfilerTheme for ProfilerTheme { + fn background_color(&self) -> Color { + self.background_color.get() + } + + fn corner_radius(&self) -> CornerRadius { + self.corner_radius.get() + } + + fn line_color(&self) -> Color { + self.line_color.get() + } + + fn line_width(&self) -> f32 { + self.line_width.get() + } + + fn bar_height(&self) -> f32 { + self.bar_height.get() + } + + fn bar_gap(&self) -> ScreenSize { + self.bar_gap.get() + } + + fn bar_corner_radius(&self) -> CornerRadius { + self.bar_corner_radius.get() + } + + fn bar_text_color(&self) -> Color { + self.bar_text_color.get() + } + + fn bar_text_size(&self) -> f32 { + self.bar_text_size.get() + } + + fn bar_text_offset(&self) -> ScreenPosition { + self.bar_text_offset.get() + } + + fn distance_text_size(&self) -> f32 { + self.distance_text_size.get() + } + + fn distance_text_offset(&self) -> f32 { + self.distance_text_offset.get() + } +} + #[derive(Serialize, Deserialize, PrototypeElement)] pub struct StatusBarTheme { pub background_color: Mutable, @@ -676,93 +1063,6 @@ impl Default for IndicatorTheme { } } -#[derive(Default)] -pub struct ThemeSelector; - -impl PrototypeElement for ThemeSelector { - fn to_element(&self, display: String) -> ElementCell { - let name_action = Box::new(move || vec![ClickAction::FocusNext(FocusMode::FocusNext)]); - let theme_name = TrackedState::default(); - let theme_kind = TrackedState::default(); - - let load_action = { - let mut theme_name = theme_name.clone(); - let theme_kind = theme_kind.clone(); - - Box::new(move || { - let taken_name = theme_name.take(); - let file_name = format!("client/themes/{}.ron", taken_name); - - vec![ClickAction::Event(UserEvent::SetThemeFile { - theme_file: file_name, - theme_kind: theme_kind.get(), - })] - }) - }; - - let save_action = { - let theme_kind = theme_kind.clone(); - - Box::new(move || { - vec![ClickAction::Event(UserEvent::SaveTheme { - theme_kind: theme_kind.get(), - })] - }) - }; - - let reload_action = { - let theme_kind = theme_kind.clone(); - - Box::new(move || { - vec![ClickAction::Event(UserEvent::ReloadTheme { - theme_kind: theme_kind.get(), - })] - }) - }; - - let elements = vec![ - PickList::default() - .with_options(vec![ - ("Menu", ThemeKind::Menu), - ("Main", ThemeKind::Main), - ("Game", ThemeKind::Game), - ]) - .with_selected(theme_kind) - .with_event(Box::new(Vec::new)) - .with_width(dimension_bound!(!)) - .wrap(), - InputFieldBuilder::new() - .with_state(theme_name) - .with_ghost_text("Theme name") - .with_enter_action(name_action) - .with_length(40) - .with_width_bound(dimension_bound!(75%)) - .build() - .wrap(), - ButtonBuilder::new() - .with_text("Load") - .with_event(load_action) - .with_width_bound(dimension_bound!(!)) - .build() - .wrap(), - ButtonBuilder::new() - .with_text("Save theme") - .with_event(save_action) - .with_width_bound(dimension_bound!(50%)) - .build() - .wrap(), - ButtonBuilder::new() - .with_text("Reload theme") - .with_event(reload_action) - .with_width_bound(dimension_bound!(!)) - .build() - .wrap(), - ]; - - Expandable::new(display, elements, false).wrap() - } -} - #[derive(Serialize, Deserialize, PrototypeElement)] pub struct InterfaceTheme { pub button: ButtonTheme, @@ -777,7 +1077,7 @@ pub struct InterfaceTheme { pub chat: ChatTheme, } -impl ThemeDefault for InterfaceTheme +impl ThemeDefault for InterfaceTheme where ButtonTheme: ThemeDefault, WindowTheme: ThemeDefault, @@ -806,6 +1106,60 @@ where } } +impl korangar_interface::theme::InterfaceTheme for InterfaceTheme { + type Button = ButtonTheme; + type Chat = ChatTheme; + type CloseButton = CloseButtonTheme; + type Expandable = ExpandableTheme; + type Input = InputTheme; + type Label = LabelTheme; + type Profiler = ProfilerTheme; + type Settings = InterfaceSettings; + type Slider = SliderTheme; + type Value = ValueTheme; + type Window = WindowTheme; + + fn button(&self) -> &Self::Button { + &self.button + } + + fn window(&self) -> &Self::Window { + &self.window + } + + fn expandable(&self) -> &Self::Expandable { + &self.expandable + } + + fn label(&self) -> &Self::Label { + &self.label + } + + fn value(&self) -> &Self::Value { + &self.value + } + + fn close_button(&self) -> &Self::CloseButton { + &self.close_button + } + + fn slider(&self) -> &Self::Slider { + &self.slider + } + + fn input(&self) -> &Self::Input { + &self.input + } + + fn profiler(&self) -> &Self::Profiler { + &self.profiler + } + + fn chat(&self) -> &Self::Chat { + &self.chat + } +} + #[derive(Default, Serialize, Deserialize, PrototypeElement)] pub struct GameTheme { pub overlay: OverlayTheme, @@ -818,8 +1172,9 @@ pub struct GameTheme { #[window_title("Theme Viewer")] #[window_class("theme_viewer")] pub struct Themes { - #[name("Theme selector")] - pub theme_selector: ThemeSelector, + #[cfg(feature = "debug")] + #[name("Actions")] + theme_actions: ThemeActions, #[name("Menu")] pub menu: InterfaceTheme, #[name("Main")] @@ -828,8 +1183,20 @@ pub struct Themes { pub game: GameTheme, } +impl Themes { + pub fn new(menu: InterfaceTheme, main: InterfaceTheme, game: GameTheme) -> Self { + Self { + #[cfg(feature = "debug")] + theme_actions: Default::default(), + menu, + main, + game, + } + } +} + impl InterfaceTheme { - pub fn new(theme_file: &str) -> Self + pub fn new(theme_file: &str) -> Self where Self: ThemeDefault, { @@ -848,7 +1215,7 @@ impl InterfaceTheme { std::fs::read_to_string(theme_file).ok().and_then(|data| ron::from_str(&data).ok()) } - pub fn reload(&mut self, theme_file: &str) + pub fn reload(&mut self, theme_file: &str) where Self: ThemeDefault, { diff --git a/korangar/src/interface/windows/account/login.rs b/korangar/src/interface/windows/account/login.rs new file mode 100644 index 00000000..f85c5115 --- /dev/null +++ b/korangar/src/interface/windows/account/login.rs @@ -0,0 +1,236 @@ +use std::ops::Not; + +use derive_new::new; +use korangar_interface::elements::{ + ButtonBuilder, Container, ElementWrap, FocusMode, InputFieldBuilder, PickList, StateButtonBuilder, Text, +}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainTrackedState, TrackedState, TrackedStateBinary, TrackedStateClone, TrackedStateExt}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::input::UserEvent; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceThemeKind; +use crate::interface::windows::WindowCache; +use crate::loaders::ClientInfo; +use crate::network::LoginSettings; + +#[derive(new)] +pub struct LoginWindow<'a> { + client_info: &'a ClientInfo, +} + +impl<'a> LoginWindow<'a> { + pub const WINDOW_CLASS: &'static str = "login"; +} + +impl<'a> PrototypeWindow for LoginWindow<'a> { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let mut login_settings = LoginSettings::new(); + + let options = self + .client_info + .services + .iter() + .map(|service| (service.display_name.clone().unwrap(), service.service_id())) + .collect(); + + // FIX: This will panic when no services are present. What is the correct + // behavior? + let selected_service = login_settings + .recent_service_id + // Only use the recent server if it is still in the client info + .filter(|&recent_service_id| { + self.client_info + .services + .iter() + .any(|service| service.service_id() == recent_service_id) + }) + .unwrap_or_else(|| self.client_info.services[0].service_id()); + + let saved_settings = login_settings.service_settings.entry(selected_service).or_default(); + + let username = PlainTrackedState::new(saved_settings.username.clone()); + let password = PlainTrackedState::new(saved_settings.password.clone()); + + let selected_service = PlainTrackedState::new(selected_service); + let login_settings = PlainTrackedState::new(login_settings); + + let selector = { + let username = username.clone(); + let password = password.clone(); + move || !username.get().is_empty() && !password.get().is_empty() + }; + + let service_changed = { + let mut username = username.clone(); + let mut password = password.clone(); + let mut login_settings = login_settings.clone(); + let selected_service = selected_service.clone(); + + Box::new(move || { + let service_id = selected_service.cloned(); + let saved_settings = + login_settings.mutate(|login_settings| login_settings.service_settings.entry(service_id).or_default().clone()); + + username.mutate(|username| { + *username = saved_settings.username; + }); + password.mutate(|password| { + *password = saved_settings.password; + }); + + Vec::new() + }) + }; + + let login_action = { + let username = username.clone(); + let password = password.clone(); + let mut login_settings = login_settings.clone(); + let selected_service = selected_service.clone(); + + move || { + // TODO: Deduplicate code + let service_id = selected_service.cloned(); + + login_settings.mutate(|login_settings| { + login_settings.recent_service_id = Some(service_id); + + let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + saved_settings.username = username.cloned(); + saved_settings.password = password.cloned(); + }); + + vec![ClickAction::Custom(UserEvent::LogIn { + service_id: selected_service.cloned(), + username: username.cloned(), + password: password.cloned(), + })] + } + }; + + let username_action = { + let username = username.clone(); + Box::new(move || { + username + .get() + .is_empty() + .not() + .then_some(vec![ClickAction::FocusNext(FocusMode::FocusNext)]) + .unwrap_or_default() + }) + }; + + let password_action = { + let username = username.clone(); + let password = password.clone(); + let mut login_settings = login_settings.clone(); + let selected_service = selected_service.clone(); + + Box::new(move || match password.get().is_empty() { + _ if username.get().is_empty() => vec![ClickAction::FocusNext(FocusMode::FocusPrevious)], + true => Vec::new(), + false => { + // TODO: Deduplicate code + let service_id = selected_service.cloned(); + + login_settings.mutate(|login_settings| { + login_settings.recent_service_id = Some(service_id); + + let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + saved_settings.username = username.cloned(); + saved_settings.password = password.cloned(); + }); + + vec![ClickAction::Custom(UserEvent::LogIn { + service_id: selected_service.cloned(), + username: username.cloned(), + password: password.cloned(), + })] + } + }) + }; + + let remember_username = { + let service_id = selected_service.clone(); + + login_settings.mapped(move |login_settings| &login_settings.service_settings.get(&service_id.get()).unwrap().remember_username) + }; + + let remember_password = { + let service_id = selected_service.clone(); + + login_settings.mapped(move |login_settings| &login_settings.service_settings.get(&service_id.get()).unwrap().remember_password) + }; + + let elements = vec![ + Text::default().with_text("Select service").wrap(), + PickList::default() + .with_options(options) + .with_selected(selected_service) + .with_event(service_changed) + .wrap(), + Text::default().with_text("Account data").wrap(), + InputFieldBuilder::new() + .with_state(username) + .with_ghost_text("Username") + .with_enter_action(username_action) + .with_length(24) + .build() + .wrap(), + InputFieldBuilder::new() + .with_state(password) + .with_ghost_text("Password") + .with_enter_action(password_action) + .with_length(24) + .hidden() + .build() + .wrap(), + Container::new({ + vec![ + StateButtonBuilder::new() + .with_text("Remember username") + .with_remote(remember_username.new_remote()) + .with_event(remember_username.toggle_action()) + .with_transparent_background() + .build() + .wrap(), + StateButtonBuilder::new() + .with_text("Remember password") + .with_remote(remember_password.new_remote()) + .with_event(remember_password.toggle_action()) + .with_transparent_background() + .build() + .wrap(), + ] + }) + .wrap(), + ButtonBuilder::new() + .with_text("Log in") + .with_disabled_selector(selector) + .with_event(Box::new(login_action)) + .build() + .wrap(), + ]; + + WindowBuilder::new() + .with_title("Log In".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .with_theme_kind(InterfaceThemeKind::Menu) + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/account/mod.rs b/korangar/src/interface/windows/account/mod.rs similarity index 100% rename from src/interface/windows/account/mod.rs rename to korangar/src/interface/windows/account/mod.rs diff --git a/src/interface/windows/account/select_server.rs b/korangar/src/interface/windows/account/select_server.rs similarity index 53% rename from src/interface/windows/account/select_server.rs rename to korangar/src/interface/windows/account/select_server.rs index f4346190..eff1aa2a 100644 --- a/src/interface/windows/account/select_server.rs +++ b/korangar/src/interface/windows/account/select_server.rs @@ -1,7 +1,13 @@ use derive_new::new; +use korangar_interface::elements::{ButtonBuilder, ElementWrap}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceThemeKind; +use crate::interface::windows::WindowCache; use crate::network::CharacterServerInformation; #[derive(new)] @@ -13,12 +19,17 @@ impl SelectServerWindow { pub const WINDOW_CLASS: &'static str = "service_server"; } -impl PrototypeWindow for SelectServerWindow { +impl PrototypeWindow for SelectServerWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let elements = self .servers .iter() @@ -34,9 +45,9 @@ impl PrototypeWindow for SelectServerWindow { WindowBuilder::new() .with_title("Select Server".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) - .with_theme_kind(ThemeKind::Menu) - .build(window_cache, interface_settings, available_space) + .with_theme_kind(InterfaceThemeKind::Menu) + .build(window_cache, application, available_space) } } diff --git a/src/interface/windows/cache.rs b/korangar/src/interface/windows/cache.rs similarity index 77% rename from src/interface/windows/cache.rs rename to korangar/src/interface/windows/cache.rs index 1e724220..b2444fa4 100644 --- a/src/interface/windows/cache.rs +++ b/korangar/src/interface/windows/cache.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "debug")] use crate::debug::*; -use crate::interface::{ScreenPosition, ScreenSize}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenPosition, ScreenSize}; #[derive(Serialize, Deserialize, new)] pub struct WindowState { @@ -20,20 +21,7 @@ pub struct WindowCache { } impl WindowCache { - pub fn new() -> Self { - Self::load().unwrap_or_else(|| { - #[cfg(feature = "debug")] - print_debug!( - "failed to load window cache from {}filename{}. creating empty cache", - MAGENTA, - NONE - ); - - Default::default() - }) - } - - pub fn load() -> Option { + fn load() -> Option { #[cfg(feature = "debug")] print_debug!("loading window cache from {}filename{}", MAGENTA, NONE); @@ -43,15 +31,30 @@ impl WindowCache { .map(|entries| Self { entries }) } - pub fn save(&self) { + fn save(&self) { #[cfg(feature = "debug")] print_debug!("saving window cache to {}filename{}", MAGENTA, NONE); let data = ron::ser::to_string_pretty(&self.entries, PrettyConfig::new()).unwrap(); std::fs::write("client/window_cache.ron", data).expect("unable to write file"); } +} + +impl korangar_interface::application::WindowCache for WindowCache { + fn create() -> Self { + Self::load().unwrap_or_else(|| { + #[cfg(feature = "debug")] + print_debug!( + "failed to load window cache from {}filename{}. creating empty cache", + MAGENTA, + NONE + ); + + Default::default() + }) + } - pub fn register_window(&mut self, identifier: &str, position: ScreenPosition, size: ScreenSize) { + fn register_window(&mut self, identifier: &str, position: ScreenPosition, size: ScreenSize) { if let Some(entry) = self.entries.get_mut(identifier) { entry.position = position; entry.size = size; @@ -61,19 +64,19 @@ impl WindowCache { } } - pub fn update_position(&mut self, identifier: &str, position: ScreenPosition) { + fn update_position(&mut self, identifier: &str, position: ScreenPosition) { if let Some(entry) = self.entries.get_mut(identifier) { entry.position = position; } } - pub fn update_size(&mut self, identifier: &str, size: ScreenSize) { + fn update_size(&mut self, identifier: &str, size: ScreenSize) { if let Some(entry) = self.entries.get_mut(identifier) { entry.size = size; } } - pub fn get_window_state(&self, identifier: &str) -> Option<(ScreenPosition, ScreenSize)> { + fn get_window_state(&self, identifier: &str) -> Option<(ScreenPosition, ScreenSize)> { self.entries.get(identifier).map(|entry| (entry.position, entry.size)) } } diff --git a/src/interface/windows/character/creation.rs b/korangar/src/interface/windows/character/creation.rs similarity index 55% rename from src/interface/windows/character/creation.rs rename to korangar/src/interface/windows/character/creation.rs index ba800ffc..fc1b2147 100644 --- a/src/interface/windows/character/creation.rs +++ b/korangar/src/interface/windows/character/creation.rs @@ -1,8 +1,15 @@ use derive_new::new; -use procedural::dimension_bound; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, FocusMode, InputFieldBuilder}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainTrackedState, TrackedState, TrackedStateClone}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceThemeKind; +use crate::interface::windows::WindowCache; const MINIMUM_NAME_LENGTH: usize = 4; const MAXIMUM_NAME_LENGTH: usize = 24; @@ -16,24 +23,29 @@ impl CharacterCreationWindow { pub const WINDOW_CLASS: &'static str = "character_creation"; } -impl PrototypeWindow for CharacterCreationWindow { +impl PrototypeWindow for CharacterCreationWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let name = TrackedState::::default(); + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let name = PlainTrackedState::::default(); let selector = { let name = name.clone(); - move || name.borrow().len() >= MINIMUM_NAME_LENGTH + move || name.get().len() >= MINIMUM_NAME_LENGTH }; let action = { let slot = self.slot; let name = name.clone(); - move || vec![ClickAction::Event(UserEvent::CreateCharacter(slot, name.borrow().clone()))] + move || vec![ClickAction::Custom(UserEvent::CreateCharacter(slot, name.cloned()))] }; let input_action = Box::new(move || vec![ClickAction::FocusNext(FocusMode::FocusNext)]); @@ -58,10 +70,10 @@ impl PrototypeWindow for CharacterCreationWindow { WindowBuilder::new() .with_title("Create Character".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .with_theme_kind(ThemeKind::Menu) - .build(window_cache, interface_settings, available_space) + .with_theme_kind(InterfaceThemeKind::Menu) + .build(window_cache, application, available_space) } } diff --git a/korangar/src/interface/windows/character/equipment.rs b/korangar/src/interface/windows/character/equipment.rs new file mode 100644 index 00000000..d92736ae --- /dev/null +++ b/korangar/src/interface/windows/character/equipment.rs @@ -0,0 +1,43 @@ +use derive_new::new; +use korangar_interface::elements::ElementWrap; +use korangar_interface::state::PlainRemote; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::EquipmentContainer; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; +use crate::inventory::Item; + +#[derive(new)] +pub struct EquipmentWindow { + items: PlainRemote>, +} + +impl EquipmentWindow { + pub const WINDOW_CLASS: &'static str = "equipment"; +} + +impl PrototypeWindow for EquipmentWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![EquipmentContainer::new(self.items.clone()).wrap()]; + + WindowBuilder::new() + .with_title("Equipment".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(150 > 200 < 300, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/korangar/src/interface/windows/character/hotbar.rs b/korangar/src/interface/windows/character/hotbar.rs new file mode 100644 index 00000000..30da8ab9 --- /dev/null +++ b/korangar/src/interface/windows/character/hotbar.rs @@ -0,0 +1,42 @@ +use derive_new::new; +use korangar_interface::elements::ElementWrap; +use korangar_interface::state::PlainRemote; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::HotbarContainer; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; +use crate::inventory::Skill; + +#[derive(new)] +pub struct HotbarWindow { + skills: PlainRemote<[Option; 10]>, +} + +impl HotbarWindow { + pub const WINDOW_CLASS: &'static str = "hotbar"; +} + +impl PrototypeWindow for HotbarWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![HotbarContainer::new(self.skills.clone()).wrap()]; + + WindowBuilder::new() + .with_title("Hotbar".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(300 > 400 < 500, ?)) + .with_elements(elements) + .build(window_cache, application, available_space) + } +} diff --git a/korangar/src/interface/windows/character/inventory.rs b/korangar/src/interface/windows/character/inventory.rs new file mode 100644 index 00000000..b514e081 --- /dev/null +++ b/korangar/src/interface/windows/character/inventory.rs @@ -0,0 +1,43 @@ +use derive_new::new; +use korangar_interface::elements::ElementWrap; +use korangar_interface::state::PlainRemote; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::InventoryContainer; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; +use crate::inventory::Item; + +#[derive(new)] +pub struct InventoryWindow { + items: PlainRemote>, +} + +impl InventoryWindow { + pub const WINDOW_CLASS: &'static str = "inventory"; +} + +impl PrototypeWindow for InventoryWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![InventoryContainer::new(self.items.clone()).wrap()]; + + WindowBuilder::new() + .with_title("Inventory".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(300 > 400 < 500, ? < 80%)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/character/mod.rs b/korangar/src/interface/windows/character/mod.rs similarity index 100% rename from src/interface/windows/character/mod.rs rename to korangar/src/interface/windows/character/mod.rs diff --git a/src/interface/windows/character/overview.rs b/korangar/src/interface/windows/character/overview.rs similarity index 69% rename from src/interface/windows/character/overview.rs rename to korangar/src/interface/windows/character/overview.rs index 274ae8b1..2f1401ea 100644 --- a/src/interface/windows/character/overview.rs +++ b/korangar/src/interface/windows/character/overview.rs @@ -1,21 +1,31 @@ use derive_new::new; +use korangar_interface::elements::{ButtonBuilder, ElementWrap}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; #[derive(new)] -pub struct CharacterOverviewWindow {} +pub struct CharacterOverviewWindow; impl CharacterOverviewWindow { pub const WINDOW_CLASS: &'static str = "character_overview"; } -impl PrototypeWindow for CharacterOverviewWindow { +impl PrototypeWindow for CharacterOverviewWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let elements = vec![ /*Text::default() .with_text(|| format!("base level: {}", player.get_base_level())) @@ -53,8 +63,8 @@ impl PrototypeWindow for CharacterOverviewWindow { WindowBuilder::new() .with_title("Character Overview".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/korangar/src/interface/windows/character/selection.rs b/korangar/src/interface/windows/character/selection.rs new file mode 100644 index 00000000..64414332 --- /dev/null +++ b/korangar/src/interface/windows/character/selection.rs @@ -0,0 +1,48 @@ +use derive_new::new; +use korangar_interface::elements::ElementWrap; +use korangar_interface::state::PlainRemote; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::CharacterPreview; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceThemeKind; +use crate::interface::windows::WindowCache; +use crate::network::CharacterInformation; + +#[derive(new)] +pub struct CharacterSelectionWindow { + characters: PlainRemote>, + move_request: PlainRemote>, + slot_count: usize, +} + +impl CharacterSelectionWindow { + pub const WINDOW_CLASS: &'static str = "character_selection"; +} + +impl PrototypeWindow for CharacterSelectionWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = (0..self.slot_count) + .map(|slot| CharacterPreview::new(self.characters.clone(), self.move_request.clone(), slot).wrap()) + .collect(); + + WindowBuilder::new() + .with_title("Character Selection".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(400 > 700 < 1000, ?)) + .with_elements(elements) + .with_theme_kind(InterfaceThemeKind::Menu) + .build(window_cache, application, available_space) + } +} diff --git a/korangar/src/interface/windows/character/skill_tree.rs b/korangar/src/interface/windows/character/skill_tree.rs new file mode 100644 index 00000000..ef34239f --- /dev/null +++ b/korangar/src/interface/windows/character/skill_tree.rs @@ -0,0 +1,47 @@ +use korangar_interface::elements::ElementWrap; +use korangar_interface::state::PlainRemote; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::SkillTreeContainer; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; +use crate::inventory::Skill; + +pub struct SkillTreeWindow { + skills: PlainRemote>, +} + +impl SkillTreeWindow { + pub fn new(skills: PlainRemote>) -> Self { + Self { skills } + } +} + +impl SkillTreeWindow { + pub const WINDOW_CLASS: &'static str = "skill_tree"; +} + +impl PrototypeWindow for SkillTreeWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![SkillTreeContainer::new(self.skills.clone()).wrap()]; + + WindowBuilder::new() + .with_title("Skill tree".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(300 > 400 < 500, ? < 80%)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/debug/commands.rs b/korangar/src/interface/windows/debug/commands.rs similarity index 81% rename from src/interface/windows/debug/commands.rs rename to korangar/src/interface/windows/debug/commands.rs index a2e5019c..687fa406 100644 --- a/src/interface/windows/debug/commands.rs +++ b/korangar/src/interface/windows/debug/commands.rs @@ -1,23 +1,33 @@ -use procedural::dimension_bound; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, InputFieldBuilder, Text}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainTrackedState, TrackedState, TrackedStateExt, ValueState}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; use crate::input::UserEvent; -use crate::interface::state::ValueState; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; #[derive(Default)] -pub struct CommandsWindow {} +pub struct CommandsWindow; impl CommandsWindow { pub const WINDOW_CLASS: &'static str = "commands"; } -impl PrototypeWindow for CommandsWindow { +impl PrototypeWindow for CommandsWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let input_text = TrackedState::::default(); + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let input_text = PlainTrackedState::::default(); let class_action = { let mut input_text = input_text.clone(); @@ -37,7 +47,7 @@ impl PrototypeWindow for CommandsWindow { return Vec::new(); }; - vec![ClickAction::Event(UserEvent::SendMessage(message))] + vec![ClickAction::Custom(UserEvent::SendMessage(message))] }) }; @@ -45,13 +55,13 @@ impl PrototypeWindow for CommandsWindow { let mut input_text = input_text.clone(); move || { - let message = input_text.with_mut(|text| { + let message = input_text.mutate(|text| { let message = format!("@jobchange {text}"); text.clear(); - ValueState::Mutated(message) + message }); - vec![ClickAction::Event(UserEvent::SendMessage(message))] + vec![ClickAction::Custom(UserEvent::SendMessage(message))] } }; @@ -159,9 +169,9 @@ impl PrototypeWindow for CommandsWindow { WindowBuilder::new() .with_title("Commands".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/korangar/src/interface/windows/debug/inspector.rs b/korangar/src/interface/windows/debug/inspector.rs new file mode 100644 index 00000000..deed79d1 --- /dev/null +++ b/korangar/src/interface/windows/debug/inspector.rs @@ -0,0 +1,37 @@ +use korangar_interface::elements::ElementWrap; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::debug::Measurement; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::FrameInspectorView; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +pub struct FrameInspectorWindow { + measurement: Measurement, +} + +impl FrameInspectorWindow { + pub fn new(measurement: Measurement) -> Self { + Self { measurement } + } +} + +impl PrototypeWindow for FrameInspectorWindow { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![FrameInspectorView::new(self.measurement.clone()).wrap()]; + + WindowBuilder::new() + .with_title("Frame Inspector".to_string()) + .with_size_bound(size_bound!(200 > 500 < 900, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/debug/maps.rs b/korangar/src/interface/windows/debug/maps.rs similarity index 73% rename from src/interface/windows/debug/maps.rs rename to korangar/src/interface/windows/debug/maps.rs index e540cadc..646c0d05 100644 --- a/src/interface/windows/debug/maps.rs +++ b/korangar/src/interface/windows/debug/maps.rs @@ -1,21 +1,31 @@ use cgmath::Vector2; +use korangar_interface::elements::{ButtonBuilder, ElementWrap}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; #[derive(Default)] -pub struct MapsWindow {} +pub struct MapsWindow; impl MapsWindow { pub const WINDOW_CLASS: &'static str = "maps"; } -impl PrototypeWindow for MapsWindow { +impl PrototypeWindow for MapsWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let map_warps = [ ("geffen", Vector2::new(119, 59)), ("alberta", Vector2::new(28, 234)), @@ -56,9 +66,9 @@ impl PrototypeWindow for MapsWindow { WindowBuilder::new() .with_title("Maps".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/src/interface/windows/debug/mod.rs b/korangar/src/interface/windows/debug/mod.rs similarity index 100% rename from src/interface/windows/debug/mod.rs rename to korangar/src/interface/windows/debug/mod.rs diff --git a/src/interface/windows/debug/packet.rs b/korangar/src/interface/windows/debug/packet.rs similarity index 50% rename from src/interface/windows/debug/packet.rs rename to korangar/src/interface/windows/debug/packet.rs index 37edaf96..87dfea0e 100644 --- a/src/interface/windows/debug/packet.rs +++ b/korangar/src/interface/windows/debug/packet.rs @@ -1,20 +1,32 @@ use std::cell::UnsafeCell; -use procedural::{dimension_bound, size_bound}; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, ScrollView, StateButtonBuilder, WeakElementCell}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainRemote, PlainTrackedState, Remote, TrackedState, TrackedStateBinary}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; -use crate::interface::*; +use crate::debug::RingBuffer; +use crate::input::UserEvent; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::{PacketEntry, PacketView}; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; pub struct PacketWindow { - packets: Remote>), N>>, - show_pings: TrackedState, - update: TrackedState, + packets: PlainRemote>>), N>>, + show_pings: PlainTrackedState, + update: PlainTrackedState, } impl PacketWindow { pub const WINDOW_CLASS: &'static str = "network"; - pub fn new(packets: Remote>), N>>, update: TrackedState) -> Self { - let show_pings = TrackedState::default(); + pub fn new( + packets: PlainRemote>>), N>>, + update: PlainTrackedState, + ) -> Self { + let show_pings = PlainTrackedState::default(); Self { packets, @@ -24,20 +36,25 @@ impl PacketWindow { } } -impl PrototypeWindow for PacketWindow { +impl PrototypeWindow for PacketWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let elements = vec![PacketView::new(self.packets.clone(), self.show_pings.new_remote()).wrap()]; let clear_selector = { let packets = self.packets.clone(); - move || !packets.borrow().is_empty() + move || !packets.get().is_empty() }; - let clear_action = { move || vec![ClickAction::Event(UserEvent::ClearPacketHistory)] }; + let clear_action = { move || vec![ClickAction::Custom(UserEvent::ClearPacketHistory)] }; let elements = vec![ ButtonBuilder::new() @@ -49,14 +66,14 @@ impl PrototypeWindow for PacketWindow { .wrap(), StateButtonBuilder::new() .with_text("Show pings") - .with_selector(self.show_pings.selector()) + .with_remote(self.show_pings.new_remote()) .with_event(self.show_pings.toggle_action()) .with_width_bound(dimension_bound!(33.33%)) .build() .wrap(), StateButtonBuilder::new() .with_text("Update") - .with_selector(self.update.selector()) + .with_remote(self.update.new_remote()) .with_event(self.update.toggle_action()) .with_width_bound(dimension_bound!(!)) .build() @@ -70,6 +87,6 @@ impl PrototypeWindow for PacketWindow { .with_size_bound(size_bound!(300 > 400 < 500, ? < 80%)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/korangar/src/interface/windows/debug/profiler.rs b/korangar/src/interface/windows/debug/profiler.rs new file mode 100644 index 00000000..802c97b8 --- /dev/null +++ b/korangar/src/interface/windows/debug/profiler.rs @@ -0,0 +1,159 @@ +use korangar_interface::elements::{ElementWrap, PickList, StateButtonBuilder}; +use korangar_interface::state::{PlainTrackedState, Remote, TrackedState, TrackedStateBinary, ValueState, Version}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; + +use crate::debug::{get_profiler_halted_version, is_profiler_halted, set_profiler_halted, ProfilerThread}; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::FrameView; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +/// Wrapper struct that exposes an implementation of [`TrackedState`] for the +/// halted state of the profiler. +#[derive(Default, Clone)] +struct TrackedProfilerHaltedState { + dummy_state: std::rc::Rc>, +} + +impl TrackedState for TrackedProfilerHaltedState { + type RemoteType = ProfilerHaltedRemote; + + fn set(&mut self, value: bool) { + set_profiler_halted(value); + } + + fn get(&self) -> std::cell::Ref<'_, bool> { + *self.dummy_state.borrow_mut() = is_profiler_halted(); + self.dummy_state.borrow() + } + + fn get_version(&self) -> korangar_interface::state::Version { + Version::from_raw(get_profiler_halted_version()) + } + + fn with_mut(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut bool) -> korangar_interface::state::ValueState, + { + let mut temporary_state = *self.dummy_state.borrow(); + + match closure(&mut temporary_state) { + ValueState::Mutated(return_value) => { + set_profiler_halted(temporary_state); + return_value + } + ValueState::Unchanged(return_value) => return_value, + } + } + + fn update(&mut self) { + let state = is_profiler_halted(); + set_profiler_halted(state); + } + + fn new_remote(&self) -> Self::RemoteType { + ProfilerHaltedRemote { + state: self.clone(), + version: Version::from_raw(get_profiler_halted_version()), + } + } +} + +/// Wrapper struct that exposes an implementation of [`Remote`] for the halted +/// state of the profiler. +struct ProfilerHaltedRemote { + state: TrackedProfilerHaltedState, + version: Version, +} + +impl Remote for ProfilerHaltedRemote { + type State = TrackedProfilerHaltedState; + + fn clone_state(&self) -> Self::State { + self.state.clone() + } + + fn get(&self) -> std::cell::Ref<'_, bool> { + self.state.get() + } + + fn consume_changed(&mut self) -> bool { + let version = Version::from_raw(get_profiler_halted_version()); + let changed = self.version != version; + self.version = version; + + changed + } +} + +pub struct ProfilerWindow { + always_update: PlainTrackedState, + visible_thread: PlainTrackedState, +} + +impl ProfilerWindow { + pub const WINDOW_CLASS: &'static str = "profiler"; + + pub fn new() -> Self { + Self { + always_update: PlainTrackedState::new(true), + visible_thread: PlainTrackedState::new(ProfilerThread::Main), + } + } +} + +impl PrototypeWindow for ProfilerWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let profiler_halted_state = TrackedProfilerHaltedState::default(); + + let elements = vec![ + PickList::default() + .with_options(vec![ + ("Main thread", ProfilerThread::Main), + ("Picker thread", ProfilerThread::Picker), + ("Shadow thread", ProfilerThread::Shadow), + ("Deferred thread", ProfilerThread::Deferred), + ]) + .with_selected(self.visible_thread.clone()) + .with_width(dimension_bound!(150)) + .with_event(Box::new(Vec::new)) + .wrap(), + StateButtonBuilder::new() + .with_text("Always update") + .with_event(self.always_update.toggle_action()) + .with_remote(self.always_update.new_remote()) + .with_width_bound(dimension_bound!(150)) + .build() + .wrap(), + StateButtonBuilder::new() + .with_text("Halt") + .with_remote(profiler_halted_state.new_remote()) + .with_event(profiler_halted_state.toggle_action()) + .with_width_bound(dimension_bound!(150)) + .build() + .wrap(), + ElementWrap::wrap(FrameView::new( + self.always_update.new_remote(), + self.visible_thread.new_remote(), + )), + ]; + + WindowBuilder::new() + .with_title("Profiler".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(200 > 500 < 900, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/debug/time.rs b/korangar/src/interface/windows/debug/time.rs similarity index 63% rename from src/interface/windows/debug/time.rs rename to korangar/src/interface/windows/debug/time.rs index 5d4c6f96..e3695271 100644 --- a/src/interface/windows/debug/time.rs +++ b/korangar/src/interface/windows/debug/time.rs @@ -1,19 +1,30 @@ +use korangar_interface::elements::{ButtonBuilder, ElementWrap}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; #[derive(Default)] -pub struct TimeWindow {} +pub struct TimeWindow; impl TimeWindow { pub const WINDOW_CLASS: &'static str = "time"; } -impl PrototypeWindow for TimeWindow { +impl PrototypeWindow for TimeWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { // TODO: Unify Set* events into one that takes a specific time let elements = vec![ ButtonBuilder::new() @@ -41,9 +52,9 @@ impl PrototypeWindow for TimeWindow { WindowBuilder::new() .with_title("Time".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/src/interface/windows/friends/list.rs b/korangar/src/interface/windows/friends/list.rs similarity index 53% rename from src/interface/windows/friends/list.rs rename to korangar/src/interface/windows/friends/list.rs index cf7094d6..58733f56 100644 --- a/src/interface/windows/friends/list.rs +++ b/korangar/src/interface/windows/friends/list.rs @@ -1,27 +1,40 @@ use std::cell::UnsafeCell; use derive_new::new; -use procedural::dimension_bound; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, InputFieldBuilder, WeakElementCell}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedStateTake}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; -use crate::interface::*; +use crate::input::UserEvent; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::FriendView; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; use crate::network::Friend; #[derive(new)] pub struct FriendsWindow { - friend_list: Remote>)>>, + friend_list: PlainRemote>>)>>, } impl FriendsWindow { pub const WINDOW_CLASS: &'static str = "friends"; } -impl PrototypeWindow for FriendsWindow { +impl PrototypeWindow for FriendsWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let friend_name = TrackedState::::default(); + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let friend_name = PlainTrackedState::::default(); let add_action = { let mut friend_name = friend_name.clone(); @@ -30,7 +43,7 @@ impl PrototypeWindow for FriendsWindow { let taken_string = friend_name.take(); (!taken_string.is_empty()) - .then_some(vec![ClickAction::Event(UserEvent::AddFriend(taken_string))]) + .then_some(vec![ClickAction::Custom(UserEvent::AddFriend(taken_string))]) .unwrap_or_default() }) }; @@ -56,9 +69,9 @@ impl PrototypeWindow for FriendsWindow { WindowBuilder::new() .with_title("Friends".to_string()) .with_class(Self::WINDOW_CLASS.to_owned()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/src/interface/windows/friends/mod.rs b/korangar/src/interface/windows/friends/mod.rs similarity index 100% rename from src/interface/windows/friends/mod.rs rename to korangar/src/interface/windows/friends/mod.rs diff --git a/src/interface/windows/friends/request.rs b/korangar/src/interface/windows/friends/request.rs similarity index 71% rename from src/interface/windows/friends/request.rs rename to korangar/src/interface/windows/friends/request.rs index 200f100d..44e5e61d 100644 --- a/src/interface/windows/friends/request.rs +++ b/korangar/src/interface/windows/friends/request.rs @@ -1,8 +1,12 @@ use derive_new::new; -use procedural::{dimension_bound, size_bound}; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, Text}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; use crate::network::Friend; #[derive(new)] @@ -14,8 +18,13 @@ impl FriendRequestWindow { pub const WINDOW_CLASS: &'static str = "friend_request"; } -impl PrototypeWindow for FriendRequestWindow { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { +impl PrototypeWindow for FriendRequestWindow { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let elements = vec![ Text::default() .with_text(format!("^ffaa00{}^000000 wants to be friends with you", self.friend.name)) @@ -48,6 +57,6 @@ impl PrototypeWindow for FriendRequestWindow { .with_class(Self::WINDOW_CLASS.to_owned()) .with_size_bound(size_bound!(250 > 250 < 250, ?)) .with_elements(elements) - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/src/interface/windows/generic/chat.rs b/korangar/src/interface/windows/generic/chat.rs similarity index 62% rename from src/interface/windows/generic/chat.rs rename to korangar/src/interface/windows/generic/chat.rs index 9ea2dbf6..63a1f9ba 100644 --- a/src/interface/windows/generic/chat.rs +++ b/korangar/src/interface/windows/generic/chat.rs @@ -2,16 +2,24 @@ use std::cell::RefCell; use std::rc::Rc; use derive_new::new; -use procedural::{dimension_bound, size_bound}; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, InputFieldBuilder, ScrollView}; +use korangar_interface::event::ClickAction; +use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState, TrackedStateTake}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::ChatBuilder; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::WindowCache; use crate::loaders::FontLoader; use crate::network::ChatMessage; #[derive(new)] pub struct ChatWindow { - messages: Remote>, + messages: PlainRemote>, font_loader: Rc>, } @@ -19,18 +27,23 @@ impl ChatWindow { pub const WINDOW_CLASS: &'static str = "chat"; } -impl PrototypeWindow for ChatWindow { +impl PrototypeWindow for ChatWindow { fn window_class(&self) -> Option<&str> { ChatWindow::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let input_text = TrackedState::::default(); + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let input_text = PlainTrackedState::::default(); let button_selector = { let input_text = input_text.clone(); - move || !input_text.borrow().is_empty() + move || !input_text.get().is_empty() }; let button_action = { @@ -38,7 +51,7 @@ impl PrototypeWindow for ChatWindow { move || { let message = input_text.take(); - vec![ClickAction::Event(UserEvent::SendMessage(message))] + vec![ClickAction::Custom(UserEvent::SendMessage(message))] } }; @@ -48,7 +61,7 @@ impl PrototypeWindow for ChatWindow { let message = input_text.take(); (!message.is_empty()) - .then_some(vec![ClickAction::Event(UserEvent::SendMessage(message))]) + .then_some(vec![ClickAction::Custom(UserEvent::SendMessage(message))]) .unwrap_or_default() }) }; @@ -85,8 +98,8 @@ impl PrototypeWindow for ChatWindow { WindowBuilder::new() .with_class(Self::WINDOW_CLASS.to_string()) .with_size_bound(size_bound!(200 > 500 < 800, 100 > 100 < 600)) - .with_background_color(Box::new(|theme| theme.chat.background_color.get())) + .with_background_color(Box::new(|theme: &InterfaceTheme| theme.chat.background_color.get())) .with_elements(elements) - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/korangar/src/interface/windows/generic/dialog.rs b/korangar/src/interface/windows/generic/dialog.rs new file mode 100644 index 00000000..edff9fda --- /dev/null +++ b/korangar/src/interface/windows/generic/dialog.rs @@ -0,0 +1,52 @@ +use korangar_interface::elements::ElementWrap; +use korangar_interface::state::{PlainTrackedState, TrackedState}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::elements::{DialogContainer, DialogElement}; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; +use crate::network::EntityId; + +pub struct DialogWindow { + elements: PlainTrackedState>, + npc_id: EntityId, +} + +impl DialogWindow { + pub const WINDOW_CLASS: &'static str = "dialog"; + + pub fn new(text: String, npc_id: EntityId) -> (Self, PlainTrackedState>) { + let elements = PlainTrackedState::new(vec![DialogElement::Text(text)]); + + let dialog_window = Self { + elements: elements.clone(), + npc_id, + }; + + (dialog_window, elements) + } +} + +impl PrototypeWindow for DialogWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![DialogContainer::new(self.elements.new_remote(), self.npc_id).wrap()]; + + WindowBuilder::new() + .with_title("Dialog".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .build(window_cache, application, available_space) + } +} diff --git a/korangar/src/interface/windows/generic/error.rs b/korangar/src/interface/windows/generic/error.rs new file mode 100644 index 00000000..023c55d6 --- /dev/null +++ b/korangar/src/interface/windows/generic/error.rs @@ -0,0 +1,39 @@ +use derive_new::new; +use korangar_interface::elements::{ElementWrap, Text}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::graphics::Color; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceThemeKind; +use crate::interface::windows::WindowCache; + +#[derive(new)] +pub struct ErrorWindow { + message: String, +} + +impl PrototypeWindow for ErrorWindow { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![ + Text::default() + .with_text(self.message.clone()) + .with_foreground_color(|_| Color::rgb_u8(220, 100, 100)) + .wrap(), + ]; + + WindowBuilder::new() + .with_title("Error".to_string()) + .with_size_bound(size_bound!(300 > 400 < 500, ?)) + .with_elements(elements) + .closable() + .with_theme_kind(InterfaceThemeKind::Menu) + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/generic/menu.rs b/korangar/src/interface/windows/generic/menu.rs similarity index 63% rename from src/interface/windows/generic/menu.rs rename to korangar/src/interface/windows/generic/menu.rs index 5a7ddd7b..226775e9 100644 --- a/src/interface/windows/generic/menu.rs +++ b/korangar/src/interface/windows/generic/menu.rs @@ -1,19 +1,31 @@ +use korangar_interface::elements::{ButtonBuilder, ElementWrap}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + use crate::input::UserEvent; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::WindowCache; #[derive(Default)] -pub struct MenuWindow {} +pub struct MenuWindow; impl MenuWindow { pub const WINDOW_CLASS: &'static str = "menu"; } -impl PrototypeWindow for MenuWindow { +impl PrototypeWindow for MenuWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let elements = vec![ ButtonBuilder::new() .with_text("Graphics settings") @@ -29,56 +41,56 @@ impl PrototypeWindow for MenuWindow { ButtonBuilder::new() .with_text("Render settings") .with_event(UserEvent::OpenRenderSettingsWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Map viewer") .with_event(UserEvent::OpenMapDataWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Maps") .with_event(UserEvent::OpenMapsWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Commands") .with_event(UserEvent::OpenCommandsWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Time") .with_event(UserEvent::OpenTimeWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Theme viewer") .with_event(UserEvent::OpenThemeViewerWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Profiler") .with_event(UserEvent::OpenProfilerWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), #[cfg(feature = "debug")] ButtonBuilder::new() .with_text("Packets") .with_event(UserEvent::OpenPacketWindow) - .with_foreground_color(|theme| theme.button.debug_foreground_color.get()) + .with_foreground_color(|theme: &InterfaceTheme| theme.button.debug_foreground_color.get()) .build() .wrap(), ButtonBuilder::new() @@ -92,9 +104,9 @@ impl PrototypeWindow for MenuWindow { WindowBuilder::new() .with_title("Menu".to_string()) .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/src/interface/windows/generic/mod.rs b/korangar/src/interface/windows/generic/mod.rs similarity index 100% rename from src/interface/windows/generic/mod.rs rename to korangar/src/interface/windows/generic/mod.rs diff --git a/korangar/src/interface/windows/mod.rs b/korangar/src/interface/windows/mod.rs new file mode 100644 index 00000000..f0684bb9 --- /dev/null +++ b/korangar/src/interface/windows/mod.rs @@ -0,0 +1,19 @@ +mod account; +mod cache; +mod character; +#[cfg(feature = "debug")] +mod debug; +mod friends; +mod generic; +mod mutable; +mod settings; + +pub use self::account::*; +pub use self::cache::WindowCache; +pub use self::character::*; +#[cfg(feature = "debug")] +pub use self::debug::*; +pub use self::friends::*; +pub use self::generic::*; +pub use self::mutable::*; +pub use self::settings::*; diff --git a/src/interface/windows/mutable/array.rs b/korangar/src/interface/windows/mutable/array.rs similarity index 57% rename from src/interface/windows/mutable/array.rs rename to korangar/src/interface/windows/mutable/array.rs index 536ed881..6b807d5b 100644 --- a/src/interface/windows/mutable/array.rs +++ b/korangar/src/interface/windows/mutable/array.rs @@ -2,10 +2,16 @@ use std::cmp::PartialOrd; use std::fmt::Display; use derive_new::new; +use korangar_interface::elements::{ElementWrap, Headline, Slider}; +use korangar_interface::event::ChangeEvent; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; use num::traits::NumOps; use num::{NumCast, Zero}; -use crate::interface::*; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ArrayType, ScreenSize}; +use crate::interface::windows::WindowCache; #[derive(new)] pub struct ArrayWindow @@ -21,28 +27,33 @@ where change_event: Option, } -impl PrototypeWindow for ArrayWindow +impl PrototypeWindow for ArrayWindow where T: ArrayType + 'static, T::Element: Zero + NumOps + NumCast + Copy + PartialOrd + Display + 'static, [(); T::ELEMENT_COUNT]:, { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { let mut elements = Vec::new(); let minimum_value = self.minimum_value.get_inner(); let maximum_value = self.maximum_value.get_inner(); for (index, (label, pointer)) in self.reference.get_array_fields().into_iter().enumerate() { - elements.push(Headline::new(label, Headline::DEFAULT_SIZE).wrap()); + elements.push(Headline::new(label, size_bound!(100%, 12)).wrap()); elements.push(Slider::new(pointer, minimum_value[index], maximum_value[index], self.change_event).wrap()); } WindowBuilder::new() .with_title(self.name.clone()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) .with_elements(elements) .closable() - .build(window_cache, interface_settings, available_space) + .build(window_cache, application, available_space) } } diff --git a/korangar/src/interface/windows/mutable/color.rs b/korangar/src/interface/windows/mutable/color.rs new file mode 100644 index 00000000..a8a926af --- /dev/null +++ b/korangar/src/interface/windows/mutable/color.rs @@ -0,0 +1,46 @@ +use derive_new::new; +use korangar_interface::elements::{ElementWrap, Expandable, Headline, Slider}; +use korangar_interface::event::ChangeEvent; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::graphics::Color; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +#[derive(new)] +pub struct ColorWindow { + name: String, + reference: &'static Color, + change_event: Option, +} + +impl PrototypeWindow for ColorWindow { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let rgb_elements = vec![ + Headline::new("red".to_string(), size_bound!(100%, 12)).wrap(), + Slider::new(&self.reference.red, 0.0, 1.0, self.change_event).wrap(), + Headline::new("green".to_string(), size_bound!(100%, 12)).wrap(), + Slider::new(&self.reference.green, 0.0, 1.0, self.change_event).wrap(), + Headline::new("blue".to_string(), size_bound!(100%, 12)).wrap(), + Slider::new(&self.reference.blue, 0.0, 1.0, self.change_event).wrap(), + Headline::new("alpha".to_string(), size_bound!(100%, 12)).wrap(), + Slider::new(&self.reference.alpha, 0.0, 1.0, self.change_event).wrap(), + ]; + + let elements = vec![Expandable::new("rgb".to_string(), rgb_elements, true).wrap()]; + + WindowBuilder::new() + .with_title(self.name.to_string()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/mutable/mod.rs b/korangar/src/interface/windows/mutable/mod.rs similarity index 100% rename from src/interface/windows/mutable/mod.rs rename to korangar/src/interface/windows/mutable/mod.rs diff --git a/korangar/src/interface/windows/mutable/number.rs b/korangar/src/interface/windows/mutable/number.rs new file mode 100644 index 00000000..794cf64f --- /dev/null +++ b/korangar/src/interface/windows/mutable/number.rs @@ -0,0 +1,43 @@ +use std::cmp::PartialOrd; + +use derive_new::new; +use korangar_interface::elements::{ElementWrap, Headline, Slider}; +use korangar_interface::event::ChangeEvent; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; +use num::traits::NumOps; +use num::{NumCast, Zero}; + +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +#[derive(new)] +pub struct NumberWindow { + name: String, + reference: &'static T, + minimum_value: T, + maximum_value: T, + change_event: Option, +} + +impl PrototypeWindow for NumberWindow { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![ + Headline::new("value".to_string(), size_bound!(100%, 12)).wrap(), + Slider::new(self.reference, self.minimum_value, self.maximum_value, self.change_event).wrap(), + ]; + + WindowBuilder::new() + .with_title(self.name.clone()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/korangar/src/interface/windows/settings/audio.rs b/korangar/src/interface/windows/settings/audio.rs new file mode 100644 index 00000000..5d3d3fa0 --- /dev/null +++ b/korangar/src/interface/windows/settings/audio.rs @@ -0,0 +1,36 @@ +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +#[derive(Default)] +pub struct AudioSettingsWindow; + +impl AudioSettingsWindow { + pub const WINDOW_CLASS: &'static str = "audio_settings"; +} + +impl PrototypeWindow for AudioSettingsWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![]; + + WindowBuilder::new() + .with_title("Audio Settings".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/korangar/src/interface/windows/settings/graphics.rs b/korangar/src/interface/windows/settings/graphics.rs new file mode 100644 index 00000000..ee2f41b8 --- /dev/null +++ b/korangar/src/interface/windows/settings/graphics.rs @@ -0,0 +1,90 @@ +use korangar_interface::elements::{ElementWrap, PickList, PrototypeElement, StateButtonBuilder, Text}; +use korangar_interface::state::{TrackedState, TrackedStateBinary}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::{dimension_bound, size_bound}; + +use crate::graphics::{PresentModeInfo, ShadowDetail}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +pub struct GraphicsSettingsWindow +where + Shadow: TrackedState + 'static, + Framerate: TrackedStateBinary, +{ + present_mode_info: PresentModeInfo, + shadow_detail: Shadow, + framerate_limit: Framerate, +} + +impl GraphicsSettingsWindow +where + Shadow: TrackedState + 'static, + Framerate: TrackedStateBinary, +{ + pub const WINDOW_CLASS: &'static str = "graphics_settings"; + + pub fn new(present_mode_info: PresentModeInfo, shadow_detail: Shadow, framerate_limit: Framerate) -> Self { + Self { + present_mode_info, + shadow_detail, + framerate_limit, + } + } +} + +impl PrototypeWindow for GraphicsSettingsWindow +where + Shadow: TrackedState + 'static, + Framerate: TrackedStateBinary, +{ + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let mut elements = vec![ + Text::default().with_text("Shadow detail").with_width(dimension_bound!(50%)).wrap(), + PickList::default() + .with_options(vec![ + ("Low", ShadowDetail::Low), + ("Medium", ShadowDetail::Medium), + ("High", ShadowDetail::High), + ("Ultra", ShadowDetail::Ultra), + ]) + .with_selected(self.shadow_detail.clone()) + .with_event(Box::new(Vec::new)) + .with_width(dimension_bound!(!)) + .wrap(), + application.to_element("Interface settings".to_string()), + ]; + + // TODO: Instead of not showing this option, disable the checkbox and add a + // tooltip + if self.present_mode_info.supports_immediate || self.present_mode_info.supports_mailbox { + elements.insert( + 0, + StateButtonBuilder::new() + .with_text("Framerate limit") + .with_event(self.framerate_limit.toggle_action()) + .with_remote(self.framerate_limit.new_remote()) + .build() + .wrap(), + ); + } + + WindowBuilder::new() + .with_title("Graphics Settings".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/interface/windows/settings/mod.rs b/korangar/src/interface/windows/settings/mod.rs similarity index 100% rename from src/interface/windows/settings/mod.rs rename to korangar/src/interface/windows/settings/mod.rs diff --git a/korangar/src/interface/windows/settings/render.rs b/korangar/src/interface/windows/settings/render.rs new file mode 100644 index 00000000..d9a099b5 --- /dev/null +++ b/korangar/src/interface/windows/settings/render.rs @@ -0,0 +1,144 @@ +use korangar_interface::elements::{ElementCell, ElementWrap, Expandable, StateButtonBuilder}; +use korangar_interface::state::{PlainTrackedState, TrackedStateBinary}; +use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use korangar_procedural::size_bound; + +use crate::graphics::RenderSettings; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::ScreenSize; +use crate::interface::windows::WindowCache; + +fn render_state_button(text: &'static str, state: impl TrackedStateBinary) -> ElementCell { + StateButtonBuilder::new() + .with_text(text) + .with_event(state.toggle_action()) + .with_remote(state.new_remote()) + .build() + .wrap() +} + +fn general_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![ + render_state_button("debug camera", settings.mapped(|settings| &settings.use_debug_camera)), + render_state_button("show fps", settings.mapped(|settings| &settings.show_frames_per_second)), + render_state_button("show wireframe", settings.mapped(|settings| &settings.show_wireframe)), + render_state_button("frustum culling", settings.mapped(|settings| &settings.frustum_culling)), + render_state_button("show bounding boxes", settings.mapped(|settings| &settings.show_bounding_boxes)), + ]; + + Expandable::new("general".to_string(), buttons, true).wrap() +} + +fn map_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![ + render_state_button("show map", settings.mapped(|settings| &settings.show_map)), + render_state_button("show objects", settings.mapped(|settings| &settings.show_objects)), + render_state_button("show entities", settings.mapped(|settings| &settings.show_entities)), + render_state_button("show water", settings.mapped(|settings| &settings.show_water)), + render_state_button("show indicators", settings.mapped(|settings| &settings.show_indicators)), + ]; + + Expandable::new("map".to_string(), buttons, true).wrap() +} + +fn lighting_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![ + render_state_button("ambient light", settings.mapped(|settings| &settings.show_ambient_light)), + render_state_button( + "directional light", + settings.mapped(|settings| &settings.show_directional_light), + ), + render_state_button("point lights", settings.mapped(|settings| &settings.show_point_lights)), + render_state_button("particle lights", settings.mapped(|settings| &settings.show_particle_lights)), + ]; + + Expandable::new("lighting".to_string(), buttons, true).wrap() +} + +fn shadows_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![render_state_button( + "directional shadows", + settings.mapped(|settings| &settings.show_directional_shadows), + )]; + + Expandable::new("shadows".to_string(), buttons, true).wrap() +} + +fn markers_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![ + render_state_button("object markers", settings.mapped(|settings| &settings.show_object_markers)), + render_state_button("light markers", settings.mapped(|settings| &settings.show_light_markers)), + render_state_button("sound markers", settings.mapped(|settings| &settings.show_sound_markers)), + render_state_button("effect markers", settings.mapped(|settings| &settings.show_effect_markers)), + render_state_button("particle markers", settings.mapped(|settings| &settings.show_particle_markers)), + render_state_button("entity markers", settings.mapped(|settings| &settings.show_entity_markers)), + ]; + + Expandable::new("markers".to_string(), buttons, true).wrap() +} + +fn grid_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![ + render_state_button("map tiles", settings.mapped(|settings| &settings.show_map_tiles)), + render_state_button("pathing", settings.mapped(|settings| &settings.show_pathing)), + ]; + + Expandable::new("grid".to_string(), buttons, true).wrap() +} + +fn buffers_expandable(settings: &PlainTrackedState) -> ElementCell { + let buttons = vec![ + render_state_button("diffuse buffer", settings.mapped(|settings| &settings.show_diffuse_buffer)), + render_state_button("normal buffer", settings.mapped(|settings| &settings.show_normal_buffer)), + render_state_button("water buffer", settings.mapped(|settings| &settings.show_water_buffer)), + render_state_button("depth buffer", settings.mapped(|settings| &settings.show_depth_buffer)), + render_state_button("shadow buffer", settings.mapped(|settings| &settings.show_shadow_buffer)), + render_state_button("picker buffer", settings.mapped(|settings| &settings.show_picker_buffer)), + render_state_button("font atlas", settings.mapped(|settings| &settings.show_font_atlas)), + ]; + + Expandable::new("buffers".to_string(), buttons, true).wrap() +} + +pub struct RenderSettingsWindow { + render_settings: PlainTrackedState, +} + +impl RenderSettingsWindow { + pub const WINDOW_CLASS: &'static str = "render_settings"; + + pub fn new(render_settings: PlainTrackedState) -> Self { + Self { render_settings } + } +} + +impl PrototypeWindow for RenderSettingsWindow { + fn window_class(&self) -> Option<&str> { + Self::WINDOW_CLASS.into() + } + + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { + let elements = vec![ + general_expandable(&self.render_settings), + map_expandable(&self.render_settings), + lighting_expandable(&self.render_settings), + shadows_expandable(&self.render_settings), + markers_expandable(&self.render_settings), + grid_expandable(&self.render_settings), + buffers_expandable(&self.render_settings), + ]; + + WindowBuilder::new() + .with_title("Render Settings".to_string()) + .with_class(Self::WINDOW_CLASS.to_string()) + .with_size_bound(size_bound!(200 > 300 < 400, ?)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } +} diff --git a/src/inventory/hotbar.rs b/korangar/src/inventory/hotbar.rs similarity index 70% rename from src/inventory/hotbar.rs rename to korangar/src/inventory/hotbar.rs index fcd331af..95f161f8 100644 --- a/src/inventory/hotbar.rs +++ b/korangar/src/inventory/hotbar.rs @@ -1,32 +1,30 @@ use std::cell::Ref; +use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState, TrackedStateExt}; + use super::Skill; use crate::input::HotbarSlot; -use crate::interface::{Remote, TrackedState, ValueState}; #[derive(Default)] pub struct Hotbar { - skills: TrackedState<[Option; 10]>, + skills: PlainTrackedState<[Option; 10]>, } impl Hotbar { pub fn set_slot(&mut self, skill: Skill, slot: HotbarSlot) { - self.skills.with_mut(|skills| { + self.skills.mutate(|skills| { skills[slot.0] = Some(skill); - ValueState::Mutated(()) }); } pub fn swap_slot(&mut self, source_slot: HotbarSlot, destination_slot: HotbarSlot) { if source_slot != destination_slot { - self.skills.with_mut(|skills| { + self.skills.mutate(|skills| { let first = skills[source_slot.0].take(); let second = skills[destination_slot.0].take(); skills[source_slot.0] = second; skills[destination_slot.0] = first; - - ValueState::Mutated(()) }); } } @@ -39,10 +37,10 @@ impl Hotbar { }*/ pub fn get_skill_in_slot(&self, slot: HotbarSlot) -> Ref> { - Ref::map(self.skills.borrow(), |skills| &skills[slot.0]) + Ref::map(self.skills.get(), |skills| &skills[slot.0]) } - pub fn get_skills(&self) -> Remote<[Option; 10]> { + pub fn get_skills(&self) -> PlainRemote<[Option; 10]> { self.skills.new_remote() } } diff --git a/src/inventory/mod.rs b/korangar/src/inventory/mod.rs similarity index 93% rename from src/inventory/mod.rs rename to korangar/src/inventory/mod.rs index 4ab1dead..4b37ffe1 100644 --- a/src/inventory/mod.rs +++ b/korangar/src/inventory/mod.rs @@ -3,11 +3,11 @@ mod skills; use std::sync::Arc; +use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState, TrackedStateExt, ValueState}; use vulkano::image::view::ImageView; pub use self::hotbar::Hotbar; pub use self::skills::{Skill, SkillTree}; -use crate::interface::{Remote, TrackedState, ValueState}; use crate::loaders::{GameFileLoader, ScriptLoader, TextureLoader}; use crate::network::{EquipPosition, ItemId, ItemIndex}; @@ -44,7 +44,7 @@ pub struct Item { #[derive(Default)] pub struct Inventory { - items: TrackedState>, + items: PlainTrackedState>, } impl Inventory { @@ -108,13 +108,12 @@ impl Inventory { } pub fn update_equipped_position(&mut self, index: ItemIndex, equipped_position: EquipPosition) { - self.items.with_mut(|items| { + self.items.mutate(|items| { items.iter_mut().find(|item| item.index == index).unwrap().equipped_position = equipped_position; - ValueState::Mutated(()) }); } - pub fn get_items(&self) -> Remote> { + pub fn get_items(&self) -> PlainRemote> { self.items.new_remote() } } diff --git a/src/inventory/skills.rs b/korangar/src/inventory/skills.rs similarity index 90% rename from src/inventory/skills.rs rename to korangar/src/inventory/skills.rs index cfb650e3..948e21dd 100644 --- a/src/inventory/skills.rs +++ b/korangar/src/inventory/skills.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use crate::interface::{Remote, TrackedState}; +use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState}; + use crate::loaders::{ActionLoader, Actions, AnimationState, GameFileLoader, Sprite, SpriteLoader}; use crate::network::{ClientTick, SkillId, SkillInformation, SkillLevel, SkillType}; @@ -17,7 +18,7 @@ pub struct Skill { #[derive(Default)] pub struct SkillTree { - skills: TrackedState>, + skills: PlainTrackedState>, } impl SkillTree { @@ -51,7 +52,7 @@ impl SkillTree { self.skills.set(skills); } - pub fn get_skills(&self) -> Remote> { + pub fn get_skills(&self) -> PlainRemote> { self.skills.new_remote() } } diff --git a/src/loaders/action/mod.rs b/korangar/src/loaders/action/mod.rs similarity index 96% rename from src/loaders/action/mod.rs rename to korangar/src/loaders/action/mod.rs index ed03d236..cbf977c5 100644 --- a/src/loaders/action/mod.rs +++ b/korangar/src/loaders/action/mod.rs @@ -4,9 +4,8 @@ use std::sync::Arc; use cgmath::{Array, Vector2}; use derive_new::new; -use procedural::PrototypeElement; -use ragnarok_bytes::{ByteStream, FromBytes}; -use ragnarok_procedural::{ByteConvertable, FromBytes}; +use korangar_procedural::PrototypeElement; +use ragnarok_bytes::{ByteConvertable, ByteStream, FromBytes}; use vulkano::image::view::ImageView; use super::version::InternalVersion; @@ -14,7 +13,8 @@ use super::Sprite; #[cfg(feature = "debug")] use crate::debug::*; use crate::graphics::{Color, Renderer, SpriteRenderer}; -use crate::interface::{InterfaceSettings, ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; use crate::loaders::{GameFileLoader, MinorFirst, Version, FALLBACK_ACTIONS_FILE}; use crate::network::ClientTick; @@ -122,7 +122,7 @@ impl Actions { position: ScreenPosition, camera_direction: usize, color: Color, - interface_settings: &InterfaceSettings, + application: &InterfaceSettings, ) where T: Renderer + SpriteRenderer, { @@ -159,7 +159,7 @@ impl Actions { Vector2::new(image_size[0], image_size[1]) }) .map(|component| component as f32); - let zoom = sprite_clip.zoom.unwrap_or(1.0) * interface_settings.scaling.get(); + let zoom = sprite_clip.zoom.unwrap_or(1.0) * application.get_scaling_factor(); let zoom2 = sprite_clip.zoom2.unwrap_or_else(|| Vector2::from_value(1.0)); let final_size = dimesions.zip(zoom2, f32::mul) * zoom; diff --git a/src/loaders/archive/folder/mod.rs b/korangar/src/loaders/archive/folder/mod.rs similarity index 100% rename from src/loaders/archive/folder/mod.rs rename to korangar/src/loaders/archive/folder/mod.rs diff --git a/src/loaders/archive/mod.rs b/korangar/src/loaders/archive/mod.rs similarity index 100% rename from src/loaders/archive/mod.rs rename to korangar/src/loaders/archive/mod.rs diff --git a/src/loaders/archive/native/assettable.rs b/korangar/src/loaders/archive/native/assettable.rs similarity index 85% rename from src/loaders/archive/native/assettable.rs rename to korangar/src/loaders/archive/native/assettable.rs index 82229661..01cc2bc3 100644 --- a/src/loaders/archive/native/assettable.rs +++ b/korangar/src/loaders/archive/native/assettable.rs @@ -1,5 +1,5 @@ use derive_new::new; -use ragnarok_procedural::{ByteConvertable, FixedByteSize}; +use ragnarok_bytes::{ByteConvertable, FixedByteSize}; /// Stores the table of files the parent GRF is holding. #[derive(Clone, ByteConvertable, FixedByteSize, new)] diff --git a/src/loaders/archive/native/builder.rs b/korangar/src/loaders/archive/native/builder.rs similarity index 100% rename from src/loaders/archive/native/builder.rs rename to korangar/src/loaders/archive/native/builder.rs diff --git a/src/loaders/archive/native/filetablerow.rs b/korangar/src/loaders/archive/native/filetablerow.rs similarity index 88% rename from src/loaders/archive/native/filetablerow.rs rename to korangar/src/loaders/archive/native/filetablerow.rs index 76ce7a2f..ba7d9b50 100644 --- a/src/loaders/archive/native/filetablerow.rs +++ b/korangar/src/loaders/archive/native/filetablerow.rs @@ -1,4 +1,4 @@ -use ragnarok_procedural::ByteConvertable; +use ragnarok_bytes::ByteConvertable; /// Represents file information about each of the files stored in the GRF. #[derive(Clone, Debug, ByteConvertable)] diff --git a/src/loaders/archive/native/header.rs b/korangar/src/loaders/archive/native/header.rs similarity index 91% rename from src/loaders/archive/native/header.rs rename to korangar/src/loaders/archive/native/header.rs index e8889937..dffe7078 100644 --- a/src/loaders/archive/native/header.rs +++ b/korangar/src/loaders/archive/native/header.rs @@ -1,5 +1,5 @@ use derive_new::new; -use ragnarok_procedural::{ByteConvertable, FixedByteSize}; +use ragnarok_bytes::{ByteConvertable, FixedByteSize}; /// Represents the Header of the GRF file. #[derive(Clone, ByteConvertable, FixedByteSize, new)] diff --git a/src/loaders/archive/native/mod.rs b/korangar/src/loaders/archive/native/mod.rs similarity index 100% rename from src/loaders/archive/native/mod.rs rename to korangar/src/loaders/archive/native/mod.rs diff --git a/src/loaders/effect/mod.rs b/korangar/src/loaders/effect/mod.rs similarity index 99% rename from src/loaders/effect/mod.rs rename to korangar/src/loaders/effect/mod.rs index 60e69a75..038f39f3 100644 --- a/src/loaders/effect/mod.rs +++ b/korangar/src/loaders/effect/mod.rs @@ -3,9 +3,8 @@ use std::sync::Arc; use cgmath::{Vector2, Vector3}; use derive_new::new; -use procedural::PrototypeElement; +use korangar_procedural::PrototypeElement; use ragnarok_bytes::{ByteStream, FromBytes}; -use ragnarok_procedural::FromBytes; use vulkano::image::view::ImageView; use super::version::InternalVersion; diff --git a/src/loaders/font/mod.rs b/korangar/src/loaders/font/mod.rs similarity index 76% rename from src/loaders/font/mod.rs rename to korangar/src/loaders/font/mod.rs index 64e007c8..48d5de10 100644 --- a/src/loaders/font/mod.rs +++ b/korangar/src/loaders/font/mod.rs @@ -1,8 +1,11 @@ use std::sync::Arc; use cgmath::{Array, Vector2}; +use korangar_interface::application::FontSizeTrait; +use korangar_interface::elements::ElementDisplay; use rusttype::gpu_cache::Cache; use rusttype::*; +use serde::{Deserialize, Serialize}; use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; use vulkano::command_buffer::{ AutoCommandBufferBuilder, BufferImageCopy, ClearColorImageInfo, CommandBufferUsage, CopyBufferToImageInfo, PrimaryCommandBufferAbstract, @@ -17,6 +20,78 @@ use vulkano::sync::GpuFuture; use super::GameFileLoader; use crate::graphics::{Color, CommandBuilder, MemoryAllocator}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ArrayType, ScreenSize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct FontSize(f32); + +impl ArrayType for FontSize { + type Element = f32; + + const ELEMENT_COUNT: usize = 1; + + fn get_array_fields(&'static self) -> [(String, &'static Self::Element); Self::ELEMENT_COUNT] { + [("size".to_owned(), &self.0)] + } + + fn get_inner(&self) -> [Self::Element; Self::ELEMENT_COUNT] { + [self.0] + } +} + +impl ElementDisplay for FontSize { + fn display(&self) -> String { + format!("^FFBB00F^000000{}", self.0.display()) + } +} + +impl korangar_interface::application::FontSizeTrait for FontSize { + fn new(value: f32) -> Self { + Self(value) + } + + fn get_value(&self) -> f32 { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Scaling(f32); + +impl ArrayType for Scaling { + type Element = f32; + + const ELEMENT_COUNT: usize = 1; + + fn get_array_fields(&'static self) -> [(String, &'static Self::Element); Self::ELEMENT_COUNT] { + [("scale".to_owned(), &self.0)] + } + + fn get_inner(&self) -> [Self::Element; Self::ELEMENT_COUNT] { + [self.0] + } +} + +impl ElementDisplay for Scaling { + fn display(&self) -> String { + format!("^FFBB00a^000000{}", self.0.display(),) + } +} + +impl Scaling { + pub fn new(value: f32) -> Self { + Self(value) + } +} + +impl korangar_interface::application::ScalingTrait for Scaling { + fn get_factor(&self) -> f32 { + self.0 + } +} pub struct FontLoader { memory_allocator: Arc, @@ -149,26 +224,35 @@ impl FontLoader { } } - pub fn get_text_dimensions(&self, text: &str, font_size: f32, available_width: f32) -> Vector2 { + pub fn get_text_dimensions(&self, text: &str, font_size: FontSize, available_width: f32) -> ScreenSize { let (_, size) = layout_paragraph( &self.font, - Scale::uniform(font_size), + Scale::uniform(font_size.get_value()), available_width, text, Color::monochrome_u8(0), ); - size + ScreenSize { + width: size.x, + height: size.y, + } } pub fn get( &mut self, text: &str, default_color: Color, - font_size: f32, + font_size: FontSize, available_width: f32, ) -> (Vec<(Rect, Rect, Color)>, f32) { - let (glyphs, size) = layout_paragraph(&self.font, Scale::uniform(font_size), available_width, text, default_color); + let (glyphs, size) = layout_paragraph( + &self.font, + Scale::uniform(font_size.get_value()), + available_width, + text, + default_color, + ); for glyph in &glyphs { self.cache.queue_glyph(0, glyph.glyph.clone()); @@ -249,3 +333,9 @@ impl FontLoader { self.font_atlas.clone() } } + +impl korangar_interface::application::FontLoaderTrait for std::rc::Rc> { + fn get_text_dimensions(&self, text: &str, font_size: FontSize, available_width: f32) -> ScreenSize { + self.borrow().get_text_dimensions(text, font_size, available_width) + } +} diff --git a/src/loaders/gamefile/list.rs b/korangar/src/loaders/gamefile/list.rs similarity index 91% rename from src/loaders/gamefile/list.rs rename to korangar/src/loaders/gamefile/list.rs index aa91ec87..a00c0086 100644 --- a/src/loaders/gamefile/list.rs +++ b/korangar/src/loaders/gamefile/list.rs @@ -1,11 +1,11 @@ -use procedural::PrototypeElement; +use korangar_procedural::PrototypeElement; use serde::{Deserialize, Serialize}; #[cfg(feature = "debug")] use crate::debug::*; const FILENAME: &str = "client/game_archives.ron"; -const DEFAULT_FILES: &[&str] = &["data.grf", "rdata.grf", "korangar/"]; +const DEFAULT_FILES: &[&str] = &["data.grf", "rdata.grf", "archive/"]; #[derive(Serialize, Deserialize, PrototypeElement)] pub(super) struct GameArchiveList { diff --git a/src/loaders/gamefile/mod.rs b/korangar/src/loaders/gamefile/mod.rs similarity index 100% rename from src/loaders/gamefile/mod.rs rename to korangar/src/loaders/gamefile/mod.rs diff --git a/src/loaders/map/data.rs b/korangar/src/loaders/map/data.rs similarity index 98% rename from src/loaders/map/data.rs rename to korangar/src/loaders/map/data.rs index 5f8d5b5c..c458d238 100644 --- a/src/loaders/map/data.rs +++ b/korangar/src/loaders/map/data.rs @@ -1,6 +1,5 @@ -use procedural::{PrototypeElement, PrototypeWindow}; +use korangar_procedural::{PrototypeElement, PrototypeWindow}; use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, ConversionResultExt, FromBytes}; -use ragnarok_procedural::FromBytes; pub use super::resource::MapResources; use crate::graphics::ColorBGRA; diff --git a/src/loaders/map/mod.rs b/korangar/src/loaders/map/mod.rs similarity index 100% rename from src/loaders/map/mod.rs rename to korangar/src/loaders/map/mod.rs diff --git a/src/loaders/map/resource.rs b/korangar/src/loaders/map/resource.rs similarity index 98% rename from src/loaders/map/resource.rs rename to korangar/src/loaders/map/resource.rs index 8e14bb79..e5c20fd5 100644 --- a/src/loaders/map/resource.rs +++ b/korangar/src/loaders/map/resource.rs @@ -1,7 +1,6 @@ use cgmath::Vector3; -use procedural::PrototypeElement; +use korangar_procedural::PrototypeElement; use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, ConversionResultExt, FromBytes}; -use ragnarok_procedural::FromBytes; use crate::graphics::{ColorRGB, Transform}; use crate::world::{EffectSource, LightSource, SoundSource}; diff --git a/src/loaders/map/vertices.rs b/korangar/src/loaders/map/vertices.rs similarity index 100% rename from src/loaders/map/vertices.rs rename to korangar/src/loaders/map/vertices.rs diff --git a/src/loaders/mod.rs b/korangar/src/loaders/mod.rs similarity index 92% rename from src/loaders/mod.rs rename to korangar/src/loaders/mod.rs index c483b1d9..0b62fd44 100644 --- a/src/loaders/mod.rs +++ b/korangar/src/loaders/mod.rs @@ -13,7 +13,7 @@ mod version; pub use self::action::*; pub use self::effect::{EffectHolder, EffectLoader, *}; -pub use self::font::FontLoader; +pub use self::font::{FontLoader, FontSize, Scaling}; pub use self::gamefile::*; #[cfg(feature = "debug")] pub use self::map::MapData; diff --git a/src/loaders/model/mod.rs b/korangar/src/loaders/model/mod.rs similarity index 97% rename from src/loaders/model/mod.rs rename to korangar/src/loaders/model/mod.rs index 52e68454..0ac8b2b8 100644 --- a/src/loaders/model/mod.rs +++ b/korangar/src/loaders/model/mod.rs @@ -3,9 +3,10 @@ use std::sync::Arc; use cgmath::{Matrix3, Matrix4, Quaternion, Rad, SquareMatrix, Vector2, Vector3}; use derive_new::new; -use procedural::PrototypeElement; +use korangar_interface::application::Application; +use korangar_interface::elements::{ElementCell, PrototypeElement}; +use korangar_procedural::PrototypeElement; use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, ConversionResultExt, FromBytes, FromBytesExt}; -use ragnarok_procedural::FromBytes; use vulkano::image::view::ImageView; use super::version::InternalVersion; @@ -104,8 +105,11 @@ impl FromBytes for ModelString { } } -impl crate::interface::PrototypeElement for ModelString { - fn to_element(&self, display: String) -> crate::interface::ElementCell { +impl PrototypeElement for ModelString +where + App: Application, +{ + fn to_element(&self, display: String) -> ElementCell { self.inner.to_element(display) } } diff --git a/src/loaders/script/mod.rs b/korangar/src/loaders/script/mod.rs similarity index 100% rename from src/loaders/script/mod.rs rename to korangar/src/loaders/script/mod.rs diff --git a/src/loaders/server/client_info.rs b/korangar/src/loaders/server/client_info.rs similarity index 100% rename from src/loaders/server/client_info.rs rename to korangar/src/loaders/server/client_info.rs diff --git a/src/loaders/server/mod.rs b/korangar/src/loaders/server/mod.rs similarity index 100% rename from src/loaders/server/mod.rs rename to korangar/src/loaders/server/mod.rs diff --git a/src/loaders/sprite/mod.rs b/korangar/src/loaders/sprite/mod.rs similarity index 99% rename from src/loaders/sprite/mod.rs rename to korangar/src/loaders/sprite/mod.rs index 9e4e0492..5b9f6620 100644 --- a/src/loaders/sprite/mod.rs +++ b/korangar/src/loaders/sprite/mod.rs @@ -2,9 +2,8 @@ use std::collections::HashMap; use std::sync::Arc; use derive_new::new; -use procedural::PrototypeElement; +use korangar_procedural::PrototypeElement; use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, ConversionResultExt, FromBytes, FromBytesExt}; -use ragnarok_procedural::FromBytes; use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; use vulkano::command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract, diff --git a/src/loaders/texture/mod.rs b/korangar/src/loaders/texture/mod.rs similarity index 100% rename from src/loaders/texture/mod.rs rename to korangar/src/loaders/texture/mod.rs diff --git a/src/loaders/version.rs b/korangar/src/loaders/version.rs similarity index 100% rename from src/loaders/version.rs rename to korangar/src/loaders/version.rs diff --git a/src/main.rs b/korangar/src/main.rs similarity index 83% rename from src/main.rs rename to korangar/src/main.rs index 684739e7..6d085a80 100644 --- a/src/main.rs +++ b/korangar/src/main.rs @@ -14,7 +14,6 @@ #![feature(negative_impls)] #![feature(option_zip)] #![feature(proc_macro_hygiene)] -#![feature(specialization)] #![feature(thread_local)] #![feature(type_changing_struct_update)] #![feature(variant_count)] @@ -40,8 +39,11 @@ use std::sync::Arc; use cgmath::{Vector2, Vector3, Zero}; use image::io::Reader as ImageReader; use image::{EncodableLayout, ImageFormat}; +use korangar_interface::application::{Application, FocusState, FontSizeTrait, FontSizeTraitExt, PositionTraitExt}; +use korangar_interface::state::{PlainTrackedState, Remote, RemoteClone, TrackedState, TrackedStateVec}; +use korangar_interface::Interface; +use korangar_procedural::debug_condition; use network::SkillType; -use procedural::debug_condition; use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; use vulkano::instance::debug::DebugUtilsMessengerCallback; #[cfg(feature = "debug")] @@ -57,8 +59,19 @@ use winit::window::{Icon, WindowBuilder}; #[cfg(feature = "debug")] use crate::debug::*; use crate::graphics::*; -use crate::input::{FocusState, InputSystem, UserEvent}; -use crate::interface::*; +use crate::input::{InputSystem, UserEvent}; +use crate::interface::application::InterfaceSettings; +use crate::interface::cursor::{MouseCursor, MouseCursorState}; +use crate::interface::dialog::DialogSystem; +use crate::interface::layout::{ScreenPosition, ScreenSize}; +use crate::interface::resource::{ItemSource, Move, SkillSource}; +use crate::interface::windows::{ + AudioSettingsWindow, CharacterCreationWindow, CharacterOverviewWindow, CharacterSelectionWindow, ChatWindow, DialogWindow, + EquipmentWindow, ErrorWindow, FriendRequestWindow, GraphicsSettingsWindow, HotbarWindow, InventoryWindow, LoginWindow, MenuWindow, + SelectServerWindow, SkillTreeWindow, +}; +#[cfg(feature = "debug")] +use crate::interface::windows::{CommandsWindow, MapsWindow, ProfilerWindow, RenderSettingsWindow, TimeWindow}; use crate::inventory::{Hotbar, Inventory, SkillTree}; use crate::loaders::*; use crate::network::{ChatMessage, NetworkEvent, NetworkingSystem, SkillId, UnitId}; @@ -112,7 +125,7 @@ fn main() { let timer = Timer::new("create window"); // TODO: move this somewhere else - let file_data = include_bytes!("../korangar/data/icon.png"); + let file_data = include_bytes!("../archive/data/icon.png"); let reader = ImageReader::with_format(Cursor::new(file_data), ImageFormat::Png); let image_buffer = reader.decode().unwrap().to_rgba8(); @@ -258,6 +271,17 @@ fn main() { swapchain_holder.window_size_u32(), ); + fn handle_result( + application: &InterfaceSettings, + interface: &mut Interface, + focus_state: &mut FocusState, + result: Result, + ) { + if let Err(message) = result { + interface.open_window(application, focus_state, &ErrorWindow::new(message)); + } + } + let mut picker_renderer = PickerRenderer::new( memory_allocator.clone(), queue.clone(), @@ -274,15 +298,17 @@ fn main() { let timer = Timer::new("load settings"); let mut input_system = InputSystem::new(); - let mut graphics_settings = GraphicsSettings::new(); + let graphics_settings = PlainTrackedState::new(GraphicsSettings::new()); + + let mut shadow_detail = graphics_settings.mapped(|settings| &settings.shadow_detail).new_remote(); + let mut framerate_limit = graphics_settings.mapped(|settings| &settings.frame_limit).new_remote(); + #[cfg(feature = "debug")] - let mut render_settings = RenderSettings::new(); + let render_settings = PlainTrackedState::new(RenderSettings::new()); #[cfg(feature = "debug")] timer.stop(); - let mut shadow_detail = Remote::new(graphics_settings.shadow_detail); - #[cfg(feature = "debug")] let timer = Timer::new("create render targets"); @@ -312,13 +338,12 @@ fn main() { #[cfg(feature = "debug")] let timer = Timer::new("initialize interface"); - let mut interface = Interface::new( - &mut game_file_loader, - &mut sprite_loader, - &mut action_loader, - swapchain_holder.window_screen_size(), - ); + let mut application = InterfaceSettings::new(); + let mut interface = korangar_interface::Interface::new(swapchain_holder.window_screen_size()); let mut focus_state = FocusState::default(); + let mut mouse_cursor = MouseCursor::new(&mut game_file_loader, &mut sprite_loader, &mut action_loader); + let mut dialog_system = DialogSystem::default(); + let mut show_interface = true; #[cfg(feature = "debug")] timer.stop(); @@ -352,7 +377,7 @@ fn main() { let client_info = load_client_info(&mut game_file_loader); let mut networking_system = NetworkingSystem::new(); - interface.open_window(&mut focus_state, &LoginWindow::new(&client_info)); + interface.open_window(&application, &mut focus_state, &LoginWindow::new(&client_info)); #[cfg(feature = "debug")] timer.stop(); @@ -369,7 +394,7 @@ fn main() { env!("CARGO_PKG_VERSION") ); let welcome_message = ChatMessage::new(welcome_string, Color::monochrome_u8(255)); - let mut chat_messages = TrackedState::new(vec![welcome_message]); + let mut chat_messages = PlainTrackedState::new(vec![welcome_message]); let thread_pool = rayon::ThreadPoolBuilder::new().num_threads(3).build().unwrap(); @@ -378,7 +403,12 @@ fn main() { Event::WindowEvent { event: WindowEvent::CloseRequested, .. - } => control_flow.set_exit(), + } => { + // FIX: For some reason GraphicsSettings is not dropped unless we use it in this + // scope. This fixes it. + let _ = &graphics_settings; + control_flow.set_exit() + } Event::WindowEvent { event: WindowEvent::Resized(_), .. @@ -407,11 +437,11 @@ fn main() { Event::WindowEvent { event: WindowEvent::CursorLeft { .. }, .. - } => interface.hide_mouse_cursor(), + } => mouse_cursor.hide(), Event::WindowEvent { event: WindowEvent::CursorEntered { .. }, .. - } => interface.show_mouse_cursor(), + } => mouse_cursor.show(), Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. @@ -458,8 +488,10 @@ fn main() { let (user_events, hovered_element, focused_element, mouse_target) = input_system.user_events( &mut interface, + &application, &mut focus_state, &mut picker_targets[swapchain_holder.get_image_number()], + &mut mouse_cursor, #[cfg(feature = "debug")] &render_settings, swapchain_holder.window_size(), @@ -477,9 +509,9 @@ fn main() { } match entity.get_entity_type() { - EntityType::Npc => interface.set_mouse_cursor_state(MouseCursorState::Dialog, client_tick), - EntityType::Warp => interface.set_mouse_cursor_state(MouseCursorState::Warp, client_tick), - EntityType::Monster => interface.set_mouse_cursor_state(MouseCursorState::Attack, client_tick), + EntityType::Npc => mouse_cursor.set_state(MouseCursorState::Dialog, client_tick), + EntityType::Warp => mouse_cursor.set_state(MouseCursorState::Warp, client_tick), + EntityType::Monster => mouse_cursor.set_state(MouseCursorState::Attack, client_tick), _ => {} // TODO: fill other entity types } } @@ -549,9 +581,9 @@ fn main() { particle_holder.clear(); effect_holder.clear(); networking_system.map_loaded(); - // TODO: this is just a workaround until i find a better solution to make the + // TODO: This is just a workaround until I find a better solution to make the // cursor always look correct. - interface.set_start_time(client_tick); + mouse_cursor.set_start_time(client_tick); } NetworkEvent::SetPlayerPosition(player_position) => { entities[0].set_position(&map, player_position, client_tick); @@ -600,10 +632,14 @@ fn main() { player.update_status(status_type); } - NetworkEvent::OpenDialog(text, npc_id) => interface.open_dialog_window(&mut focus_state, text, npc_id), - NetworkEvent::AddNextButton => interface.add_next_button(), - NetworkEvent::AddCloseButton => interface.add_close_button(), - NetworkEvent::AddChoiceButtons(choices) => interface.add_choice_buttons(choices), + NetworkEvent::OpenDialog(text, npc_id) => { + if let Some(dialog_window) = dialog_system.open_dialog_window(text, npc_id) { + interface.open_window(&application, &mut focus_state, &dialog_window); + } + } + NetworkEvent::AddNextButton => dialog_system.add_next_button(), + NetworkEvent::AddCloseButton => dialog_system.add_close_button(), + NetworkEvent::AddChoiceButtons(choices) => dialog_system.add_choice_buttons(choices), NetworkEvent::AddQuestEffect(quest_effect) => { particle_holder.add_quest_icon(&mut game_file_loader, &mut texture_loader, &map, quest_effect) } @@ -653,12 +689,14 @@ fn main() { interface.close_all_windows_except(&mut focus_state); let character_selection_window = networking_system.character_selection_window(); - interface.open_window(&mut focus_state, &character_selection_window); + interface.open_window(&application, &mut focus_state, &character_selection_window); start_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); directional_shadow_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); } - NetworkEvent::FriendRequest(friend) => interface.open_window(&mut focus_state, &FriendRequestWindow::new(friend)), + NetworkEvent::FriendRequest(friend) => { + interface.open_window(&application, &mut focus_state, &FriendRequestWindow::new(friend)) + } NetworkEvent::VisualEffect(path, entity_id) => { let effect = effect_loader.get(path, &mut game_file_loader, &mut texture_loader).unwrap(); let frame_timer = effect.new_frame_timer(); @@ -744,9 +782,9 @@ fn main() { // that will be problematic interface.close_window_with_class(&mut focus_state, LoginWindow::WINDOW_CLASS); - interface.open_window(&mut focus_state, &SelectServerWindow::new(servers)); + interface.open_window(&application, &mut focus_state, &SelectServerWindow::new(servers)); } - Err(message) => interface.open_window(&mut focus_state, &ErrorWindow::new(message)), + Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), } } UserEvent::SelectServer(server) => { @@ -757,56 +795,62 @@ fn main() { interface.close_window_with_class(&mut focus_state, SelectServerWindow::WINDOW_CLASS); let character_selection_window = networking_system.character_selection_window(); - interface.open_window(&mut focus_state, &character_selection_window); + interface.open_window(&application, &mut focus_state, &character_selection_window); } - Err(message) => interface.open_window(&mut focus_state, &ErrorWindow::new(message)), + Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), } } UserEvent::LogOut => networking_system.log_out().unwrap(), UserEvent::Exit => *control_flow = ControlFlow::Exit, UserEvent::CameraZoom(factor) => player_camera.soft_zoom(factor), UserEvent::CameraRotate(factor) => player_camera.soft_rotate(factor), - UserEvent::ToggleFrameLimit => { - graphics_settings.toggle_frame_limit(); - swapchain_holder.set_frame_limit(present_mode_info, graphics_settings.frame_limit); - - // NOTE: For some reason the interface buffer becomes messed up when - // recreating the swapchain, so we need to render it again. - interface.schedule_render(); - } - UserEvent::ToggleShowInterface => graphics_settings.toggle_show_interface(), UserEvent::OpenMenuWindow => { if !entities.is_empty() { - interface.open_window(&mut focus_state, &MenuWindow::default()) + interface.open_window(&application, &mut focus_state, &MenuWindow::default()) } } UserEvent::OpenInventoryWindow => { if !entities.is_empty() { - interface.open_window(&mut focus_state, &InventoryWindow::new(player_inventory.get_items())) + interface.open_window( + &application, + &mut focus_state, + &InventoryWindow::new(player_inventory.get_items()), + ) } } UserEvent::OpenEquipmentWindow => { if !entities.is_empty() { - interface.open_window(&mut focus_state, &EquipmentWindow::new(player_inventory.get_items())) + interface.open_window( + &application, + &mut focus_state, + &EquipmentWindow::new(player_inventory.get_items()), + ) } } UserEvent::OpenSkillTreeWindow => { if !entities.is_empty() { - interface.open_window(&mut focus_state, &SkillTreeWindow::new(player_skill_tree.get_skills())) + interface.open_window( + &application, + &mut focus_state, + &SkillTreeWindow::new(player_skill_tree.get_skills()), + ) } } UserEvent::OpenGraphicsSettingsWindow => interface.open_window( + &application, &mut focus_state, - &GraphicsSettingsWindow::new(present_mode_info, shadow_detail.clone_state()), + &GraphicsSettingsWindow::new(present_mode_info, shadow_detail.clone_state(), framerate_limit.clone_state()), ), - UserEvent::OpenAudioSettingsWindow => interface.open_window(&mut focus_state, &AudioSettingsWindow::default()), - UserEvent::OpenFriendsWindow => interface.open_window(&mut focus_state, &networking_system.friends_window()), - UserEvent::SetThemeFile { theme_file, theme_kind } => { - interface.set_theme_file(theme_file, theme_kind); - interface.reload_theme(theme_kind); + UserEvent::OpenAudioSettingsWindow => { + interface.open_window(&application, &mut focus_state, &AudioSettingsWindow::default()) } - UserEvent::SaveTheme { theme_kind } => interface.save_theme(theme_kind), - UserEvent::ReloadTheme { theme_kind } => interface.reload_theme(theme_kind), + UserEvent::OpenFriendsWindow => { + interface.open_window(&application, &mut focus_state, &networking_system.friends_window()) + } + UserEvent::ToggleShowInterface => show_interface = !show_interface, + UserEvent::SetThemeFile { theme_file, theme_kind } => application.set_theme_file(theme_file, theme_kind), + UserEvent::SaveTheme { theme_kind } => application.save_theme(theme_kind), + UserEvent::ReloadTheme { theme_kind } => application.reload_theme(theme_kind), UserEvent::SelectCharacter(character_slot) => { match networking_system.select_character(character_slot) { Ok((account_id, character_information, map_name)) => { @@ -839,40 +883,47 @@ fn main() { // TODO: this will do one unnecessary restore_focus. check if // that will be problematic interface.close_window_with_class(&mut focus_state, CharacterSelectionWindow::WINDOW_CLASS); - interface.open_window(&mut focus_state, &CharacterOverviewWindow::new()); + interface.open_window(&application, &mut focus_state, &CharacterOverviewWindow::new()); interface.open_window( + &application, &mut focus_state, &ChatWindow::new(chat_messages.new_remote(), font_loader.clone()), ); - interface.open_window(&mut focus_state, &HotbarWindow::new(hotbar.get_skills())); + interface.open_window(&application, &mut focus_state, &HotbarWindow::new(hotbar.get_skills())); particle_holder.clear(); networking_system.map_loaded(); - // TODO: this is just a workaround until i find a better solution to make the + // TODO: This is just a workaround until I find a better solution to make the // cursor always look correct. - interface.set_start_time(client_tick); + mouse_cursor.set_start_time(client_tick); game_timer.set_client_tick(client_tick); } - Err(message) => interface.open_window(&mut focus_state, &ErrorWindow::new(message)), + Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), } } UserEvent::OpenCharacterCreationWindow(character_slot) => { - interface.open_window(&mut focus_state, &CharacterCreationWindow::new(character_slot)) + interface.open_window(&application, &mut focus_state, &CharacterCreationWindow::new(character_slot)) } UserEvent::CreateCharacter(character_slot, name) => { match networking_system.create_character(character_slot, name) { Ok(..) => interface.close_window_with_class(&mut focus_state, CharacterCreationWindow::WINDOW_CLASS), - Err(message) => interface.open_window(&mut focus_state, &ErrorWindow::new(message)), + Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), } } - UserEvent::DeleteCharacter(character_id) => { - interface.handle_result(&mut focus_state, networking_system.delete_character(character_id)) - } + UserEvent::DeleteCharacter(character_id) => handle_result( + &application, + &mut interface, + &mut focus_state, + networking_system.delete_character(character_id), + ), UserEvent::RequestSwitchCharacterSlot(origin_slot) => networking_system.request_switch_character_slot(origin_slot), UserEvent::CancelSwitchCharacterSlot => networking_system.cancel_switch_character_slot(), - UserEvent::SwitchCharacterSlot(destination_slot) => { - interface.handle_result(&mut focus_state, networking_system.switch_character_slot(destination_slot)) - } + UserEvent::SwitchCharacterSlot(destination_slot) => handle_result( + &application, + &mut interface, + &mut focus_state, + networking_system.switch_character_slot(destination_slot), + ), UserEvent::RequestPlayerMove(destination) => { if !entities.is_empty() { networking_system.request_player_move(destination) @@ -900,32 +951,40 @@ fn main() { UserEvent::NextDialog(npc_id) => networking_system.next_dialog(npc_id), UserEvent::CloseDialog(npc_id) => { networking_system.close_dialog(npc_id); - interface.close_dialog_window(&mut focus_state); + dialog_system.close_dialog(); + interface.close_window_with_class(&mut focus_state, DialogWindow::WINDOW_CLASS); } UserEvent::ChooseDialogOption(npc_id, option) => { networking_system.choose_dialog_option(npc_id, option); if option == -1 { - interface.close_dialog_window(&mut focus_state); + dialog_system.close_dialog(); + interface.close_window_with_class(&mut focus_state, DialogWindow::WINDOW_CLASS); } } - UserEvent::MoveItem(item_move) => match (item_move.source, item_move.destination) { - (ItemSource::Inventory, ItemSource::Equipment { position }) => { - networking_system.request_item_equip(item_move.item.index, position); - } - (ItemSource::Equipment { .. }, ItemSource::Inventory) => { - networking_system.request_item_unequip(item_move.item.index); - } - _ => {} - }, - UserEvent::MoveSkill(skill_move) => match (skill_move.source, skill_move.destination) { - (SkillSource::SkillTree, SkillSource::Hotbar { slot }) => { - hotbar.set_slot(skill_move.skill, slot); - } - (SkillSource::Hotbar { slot: source_slot }, SkillSource::Hotbar { slot: destination_slot }) => { - hotbar.swap_slot(source_slot, destination_slot); - } - _ => {} + UserEvent::MoveResource(r#move) => match r#move { + Move::Item { source, destination, item } => match (source, destination) { + (ItemSource::Inventory, ItemSource::Equipment { position }) => { + networking_system.request_item_equip(item.index, position); + } + (ItemSource::Equipment { .. }, ItemSource::Inventory) => { + networking_system.request_item_unequip(item.index); + } + _ => {} + }, + Move::Skill { + source, + destination, + skill, + } => match (source, destination) { + (SkillSource::SkillTree, SkillSource::Hotbar { slot }) => { + hotbar.set_slot(skill, slot); + } + (SkillSource::Hotbar { slot: source_slot }, SkillSource::Hotbar { slot: destination_slot }) => { + hotbar.swap_slot(source_slot, destination_slot); + } + _ => {} + }, }, UserEvent::CastSkill(slot) => { if let Some(skill) = hotbar.get_skill_in_slot(slot).as_ref() { @@ -983,23 +1042,23 @@ fn main() { interface.close_window_with_class(&mut focus_state, FriendRequestWindow::WINDOW_CLASS); } #[cfg(feature = "debug")] - UserEvent::ToggleFrustumCulling => render_settings.toggle_frustum_culling(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowBoundingBoxes => render_settings.toggle_show_bounding_boxes(), - #[cfg(feature = "debug")] UserEvent::OpenMarkerDetails(marker_identifier) => { - interface.open_window(&mut focus_state, map.resolve_marker(&entities, marker_identifier)) + interface.open_window(&application, &mut focus_state, map.resolve_marker(&entities, marker_identifier)) } #[cfg(feature = "debug")] - UserEvent::OpenRenderSettingsWindow => interface.open_window(&mut focus_state, &RenderSettingsWindow::default()), + UserEvent::OpenRenderSettingsWindow => interface.open_window( + &application, + &mut focus_state, + &RenderSettingsWindow::new(render_settings.clone()), + ), #[cfg(feature = "debug")] - UserEvent::OpenMapDataWindow => interface.open_window(&mut focus_state, map.to_prototype_window()), + UserEvent::OpenMapDataWindow => interface.open_window(&application, &mut focus_state, map.to_prototype_window()), #[cfg(feature = "debug")] - UserEvent::OpenMapsWindow => interface.open_window(&mut focus_state, &MapsWindow::default()), + UserEvent::OpenMapsWindow => interface.open_window(&application, &mut focus_state, &MapsWindow::default()), #[cfg(feature = "debug")] - UserEvent::OpenCommandsWindow => interface.open_window(&mut focus_state, &CommandsWindow::default()), + UserEvent::OpenCommandsWindow => interface.open_window(&application, &mut focus_state, &CommandsWindow::default()), #[cfg(feature = "debug")] - UserEvent::OpenTimeWindow => interface.open_window(&mut focus_state, &TimeWindow::default()), + UserEvent::OpenTimeWindow => interface.open_window(&application, &mut focus_state, &TimeWindow::default()), #[cfg(feature = "debug")] UserEvent::SetDawn => game_timer.set_day_timer(0.0), #[cfg(feature = "debug")] @@ -1009,16 +1068,18 @@ fn main() { #[cfg(feature = "debug")] UserEvent::SetMidnight => game_timer.set_day_timer(-std::f32::consts::FRAC_PI_2), #[cfg(feature = "debug")] - UserEvent::OpenThemeViewerWindow => interface.open_theme_viewer_window(&mut focus_state), + UserEvent::OpenThemeViewerWindow => { + interface.open_window(&application, &mut focus_state, application.theme_window()) + } #[cfg(feature = "debug")] - UserEvent::OpenProfilerWindow => interface.open_window(&mut focus_state, &ProfilerWindow::new()), + UserEvent::OpenProfilerWindow => interface.open_window(&application, &mut focus_state, &ProfilerWindow::new()), #[cfg(feature = "debug")] - UserEvent::OpenPacketWindow => interface.open_window(&mut focus_state, &networking_system.packet_window()), + UserEvent::OpenPacketWindow => { + interface.open_window(&application, &mut focus_state, &networking_system.packet_window()) + } #[cfg(feature = "debug")] UserEvent::ClearPacketHistory => networking_system.clear_packet_history(), #[cfg(feature = "debug")] - UserEvent::ToggleUseDebugCamera => render_settings.toggle_use_debug_camera(), - #[cfg(feature = "debug")] UserEvent::CameraLookAround(offset) => debug_camera.look_around(offset), #[cfg(feature = "debug")] UserEvent::CameraMoveForward => debug_camera.move_forward(delta_time as f32), @@ -1034,67 +1095,6 @@ fn main() { UserEvent::CameraAccelerate => debug_camera.accelerate(), #[cfg(feature = "debug")] UserEvent::CameraDecelerate => debug_camera.decelerate(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowFramesPerSecond => render_settings.toggle_show_frames_per_second(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowWireframe => { - render_settings.toggle_show_wireframe(); - swapchain_holder.invalidate_swapchain(); - - // NOTE: For some reason the interface buffer becomes messed up when - // recreating the swapchain, so we need to render it again. - interface.schedule_render(); - } - #[cfg(feature = "debug")] - UserEvent::ToggleShowMap => render_settings.toggle_show_map(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowObjects => render_settings.toggle_show_objects(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowEntities => render_settings.toggle_show_entities(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowWater => render_settings.toggle_show_water(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowIndicators => render_settings.toggle_show_indicators(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowAmbientLight => render_settings.toggle_show_ambient_light(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowDirectionalLight => render_settings.toggle_show_directional_light(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowPointLights => render_settings.toggle_show_point_lights(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowParticleLights => render_settings.toggle_show_particle_lights(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowDirectionalShadows => render_settings.toggle_show_directional_shadows(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowObjectMarkers => render_settings.toggle_show_object_markers(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowLightMarkers => render_settings.toggle_show_light_markers(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowSoundMarkers => render_settings.toggle_show_sound_markers(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowEffectMarkers => render_settings.toggle_show_effect_markers(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowParticleMarkers => render_settings.toggle_show_particle_markers(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowEntityMarkers => render_settings.toggle_show_entity_markers(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowMapTiles => render_settings.toggle_show_map_tiles(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowPathing => render_settings.toggle_show_pathing(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowDiffuseBuffer => render_settings.toggle_show_diffuse_buffer(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowNormalBuffer => render_settings.toggle_show_normal_buffer(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowWaterBuffer => render_settings.toggle_show_water_buffer(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowDepthBuffer => render_settings.toggle_show_depth_buffer(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowShadowBuffer => render_settings.toggle_show_shadow_buffer(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowPickerBuffer => render_settings.toggle_show_picker_buffer(), - #[cfg(feature = "debug")] - UserEvent::ToggleShowFontAtlas => render_settings.toggle_show_font_atlas(), } } @@ -1134,7 +1134,8 @@ fn main() { particle_holder.update(delta_time as f32); effect_holder.update(&entities, delta_time as f32); - let (clear_interface, render_interface) = interface.update(font_loader.clone(), &mut focus_state, client_tick); + let (clear_interface, render_interface) = interface.update(&application, font_loader.clone(), &mut focus_state); + mouse_cursor.update(client_tick); if swapchain_holder.is_swapchain_invalid() { #[cfg(feature = "debug")] @@ -1146,7 +1147,7 @@ fn main() { viewport.clone(), swapchain_holder.window_size_u32(), #[cfg(feature = "debug")] - render_settings.show_wireframe, + render_settings.get().show_wireframe, ); interface_renderer.recreate_pipeline(viewport.clone(), swapchain_holder.window_size_u32()); picker_renderer.recreate_pipeline(viewport, swapchain_holder.window_size_u32()); @@ -1179,7 +1180,6 @@ fn main() { profile_block!("re-create shadow maps"); let new_shadow_detail = shadow_detail.get(); - graphics_settings.shadow_detail = new_shadow_detail; directional_shadow_targets = swapchain_holder .get_swapchain_images() @@ -1188,6 +1188,14 @@ fn main() { .collect::::Target>>(); } + if framerate_limit.consume_changed() { + swapchain_holder.set_frame_limit(present_mode_info, framerate_limit.cloned()); + + // NOTE: For some reason the interface buffer becomes messed up when + // recreating the swapchain, so we need to render it again. + interface.schedule_render(); + } + #[cfg(feature = "debug")] let matrices_measuremen = start_measurement("generate view and projection matrices"); @@ -1198,7 +1206,7 @@ fn main() { player_camera.generate_view_projection(swapchain_holder.window_size()); directional_shadow_camera.generate_view_projection(swapchain_holder.window_size()); #[cfg(feature = "debug")] - if render_settings.use_debug_camera { + if render_settings.get().use_debug_camera { debug_camera.generate_view_projection(swapchain_holder.window_size()); } @@ -1207,7 +1215,7 @@ fn main() { let current_camera: &(dyn Camera + Send + Sync) = match entities.is_empty() { #[cfg(feature = "debug")] - _ if render_settings.use_debug_camera => &debug_camera, + _ if render_settings.get().use_debug_camera => &debug_camera, true => &start_camera, false => &player_camera, }; @@ -1247,7 +1255,9 @@ fn main() { #[cfg(feature = "debug")] let prepare_frame_measuremen = start_measurement("prepare frame"); - let walk_indicator_color = interface.get_game_theme().indicator.walking.get(); + #[cfg(feature = "debug")] + let render_settings = &*render_settings.get(); + let walk_indicator_color = application.get_game_theme().indicator.walking.get(); let image_number = swapchain_holder.get_image_number(); let directional_shadow_image = directional_shadow_targets[image_number].image.clone(); let screen_target = &mut screen_targets[image_number]; @@ -1447,16 +1457,10 @@ fn main() { interface_target.start(window_size_u32, clear_interface); - let state_provider = &StateProvider::new( - &graphics_settings, - #[cfg(feature = "debug")] - &render_settings, - ); - interface.render( &mut interface_target, &interface_renderer, - state_provider, + &application, hovered_element, focused_element, input_system.get_mouse_mode(), @@ -1495,13 +1499,33 @@ fn main() { screen_target, &deferred_renderer, current_camera, - interface.get_game_theme(), + application.get_game_theme(), window_size, ); if let Some(name) = &entity.get_details() { let name = name.split('#').next().unwrap(); - interface.render_hover_text(screen_target, &deferred_renderer, name, input_system.get_mouse_position()); + + let offset = ScreenPosition { + left: name.len() as f32 * -3.0, + top: 20.0, + }; + + deferred_renderer.render_text( + screen_target, + name, + input_system.get_mouse_position() + offset + ScreenPosition::uniform(1.0), + Color::monochrome_u8(0), + FontSize::new(12.0), + ); // TODO: move variables into theme + + deferred_renderer.render_text( + screen_target, + name, + input_system.get_mouse_position() + offset, + Color::monochrome_u8(255), + FontSize::new(12.0), + ); } } } @@ -1514,24 +1538,34 @@ fn main() { screen_target, &deferred_renderer, current_camera, - interface.get_game_theme(), + application.get_game_theme(), window_size, ); } #[cfg(feature = "debug")] if render_settings.show_frames_per_second { - interface.render_frames_per_second(screen_target, &deferred_renderer, game_timer.last_frames_per_second()); + let game_theme = application.get_game_theme(); + + deferred_renderer.render_text( + screen_target, + &game_timer.last_frames_per_second().to_string(), + game_theme.overlay.text_offset.get().scaled(application.get_scaling()), + game_theme.overlay.foreground_color.get(), + game_theme.overlay.font_size.get().scaled(application.get_scaling()), + ); } - if graphics_settings.show_interface { + if show_interface { deferred_renderer.overlay_interface(screen_target, interface_target.image.clone()); - interface.render_mouse_cursor( + mouse_cursor.render( screen_target, &deferred_renderer, input_system.get_mouse_position(), input_system.get_mouse_mode().grabbed(), + Color::rgb_u8(200, 100, 50), + &application, ); } diff --git a/src/network/login.rs b/korangar/src/network/login.rs similarity index 100% rename from src/network/login.rs rename to korangar/src/network/login.rs diff --git a/src/network/mod.rs b/korangar/src/network/mod.rs similarity index 98% rename from src/network/mod.rs rename to korangar/src/network/mod.rs index 2286f1b0..caa411d2 100644 --- a/src/network/mod.rs +++ b/korangar/src/network/mod.rs @@ -8,22 +8,26 @@ use std::time::Duration; use cgmath::Vector2; use chrono::Local; use derive_new::new; -use procedural::{profile, PrototypeElement}; -use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, ConversionResultExt, FromBytes, ToBytes}; -use ragnarok_procedural::{ByteConvertable, FixedByteSize, FromBytes, IncomingPacket, OutgoingPacket}; +use korangar_interface::elements::{ElementCell, ElementWrap, Expandable, PrototypeElement, WeakElementCell}; +use korangar_interface::state::{ + PlainTrackedState, TrackedState, TrackedStateClone, TrackedStateExt, TrackedStateTake, TrackedStateVec, ValueState, +}; +use korangar_procedural::{profile, PrototypeElement}; +use ragnarok_bytes::{ + ByteConvertable, ByteStream, ConversionError, ConversionResult, ConversionResultExt, FixedByteSize, FromBytes, IncomingPacket, + OutgoingPacket, ToBytes, +}; pub use self::login::LoginSettings; #[cfg(feature = "debug")] use crate::debug::*; use crate::graphics::{Color, ColorBGRA, ColorRGBA}; +use crate::interface::application::InterfaceSettings; #[cfg(feature = "debug")] -use crate::interface::PacketEntry; +use crate::interface::elements::PacketEntry; #[cfg(feature = "debug")] -use crate::interface::PacketWindow; -use crate::interface::{ - CharacterSelectionWindow, ElementCell, ElementWrap, Expandable, FriendsWindow, PrototypeElement, TrackedState, TrackedStateTake, - ValueState, WeakElementCell, -}; +use crate::interface::windows::PacketWindow; +use crate::interface::windows::{CharacterSelectionWindow, FriendsWindow}; use crate::loaders::{ClientInfo, ServiceId}; #[derive(Clone, Copy, Debug, ByteConvertable, FixedByteSize, PrototypeElement)] @@ -99,7 +103,7 @@ impl<'a> ByteStreamNetworkExt for ByteStream<'a, Vec> { /// followed by the packet data. If the packet does not have a fixed size, /// the first two bytes will be the size of the packet in bytes *including* the /// header. Packets are sent in little endian. -pub trait IncomingPacket: PrototypeElement + Clone { +pub trait IncomingPacket: PrototypeElement + Clone { const IS_PING: bool; const HEADER: u16; @@ -111,7 +115,7 @@ pub trait IncomingPacket: PrototypeElement + Clone { /// followed by the packet data. If the packet does not have a fixed size, /// the first two bytes will be the size of the packet in bytes *including* the /// header. Packets are sent in little endian. -pub trait OutgoingPacket: PrototypeElement + Clone { +pub trait OutgoingPacket: PrototypeElement + Clone { const IS_PING: bool; fn to_bytes(&self) -> ConversionResult>; @@ -1110,8 +1114,8 @@ impl FromBytes for StatusType { } // TODO: make StatusType derivable -impl PrototypeElement for StatusType { - fn to_element(&self, display: String) -> ElementCell { +impl PrototypeElement for StatusType { + fn to_element(&self, display: String) -> ElementCell { format!("{self:?}").to_element(display) } } @@ -2648,8 +2652,8 @@ impl IncomingPacket for UnknownPacket { } } -impl PrototypeElement for UnknownPacket { - fn to_element(&self, display: String) -> ElementCell { +impl PrototypeElement for UnknownPacket { + fn to_element(&self, display: String) -> ElementCell { let mut byte_stream = ByteStream::<()>::without_metadata(&self.bytes); let elements = match self.bytes.len() >= 2 { @@ -2708,8 +2712,8 @@ struct LoginData { // }, // Playing { // friend_list: TrackedState>)>>, player_name: String, -// }, +// UnsafeCell>>)>>, player_name: +// String, }, // } pub struct NetworkingSystem { @@ -2724,15 +2728,15 @@ pub struct NetworkingSystem { // TODO: Move to GameState login_data: Option, - characters: TrackedState>, - move_request: TrackedState>, - friend_list: TrackedState>)>>, + characters: PlainTrackedState>, + move_request: PlainTrackedState>, + friend_list: PlainTrackedState>>)>>, slot_count: usize, player_name: String, #[cfg(feature = "debug")] - update_packets: TrackedState, + update_packets: PlainTrackedState, #[cfg(feature = "debug")] - packet_history: TrackedState>), 256>>, + packet_history: PlainTrackedState>>), 256>>, } impl NetworkingSystem { @@ -2742,18 +2746,18 @@ impl NetworkingSystem { let map_stream = None; let map_stream_buffer = Vec::new(); let login_data = None; - let characters = TrackedState::default(); - let move_request = TrackedState::default(); - let friend_list = TrackedState::default(); + let characters = PlainTrackedState::default(); + let move_request = PlainTrackedState::default(); + let friend_list = PlainTrackedState::default(); let slot_count = 0; let login_keep_alive_timer = NetworkTimer::new(Duration::from_secs(58)); let character_keep_alive_timer = NetworkTimer::new(Duration::from_secs(10)); let map_keep_alive_timer = NetworkTimer::new(Duration::from_secs(4)); let player_name = String::new(); #[cfg(feature = "debug")] - let update_packets = TrackedState::new(true); + let update_packets = PlainTrackedState::new(true); #[cfg(feature = "debug")] - let packet_history = TrackedState::default(); + let packet_history = PlainTrackedState::default(); Self { login_stream, @@ -2945,10 +2949,9 @@ impl NetworkingSystem { #[cfg(feature = "debug")] fn update_packet_history(&mut self, mut packets: Vec) { - if self.update_packets.get() { - self.packet_history.with_mut(|buffer| { + if self.update_packets.cloned() { + self.packet_history.mutate(|buffer| { packets.drain(..).for_each(|packet| buffer.push((packet, UnsafeCell::new(None)))); - ValueState::Mutated(()) }); } } @@ -2958,13 +2961,12 @@ impl NetworkingSystem { where T: OutgoingPacket + 'static, { - if self.update_packets.get() { - self.packet_history.with_mut(|buffer| { + if self.update_packets.cloned() { + self.packet_history.mutate(|buffer| { buffer.push(( PacketEntry::new_outgoing(packet, std::any::type_name::(), T::IS_PING), UnsafeCell::new(None), )); - ValueState::Mutated(()) }); } } @@ -3234,7 +3236,7 @@ impl NetworkingSystem { let character_information = self .characters - .borrow() + .get() .iter() .find(|character| character.character_number as usize == slot) .cloned() @@ -3877,9 +3879,8 @@ impl NetworkingSystem { } FriendListPacket::HEADER => { let packet = FriendListPacket::from_bytes(byte_stream)?; - self.friend_list.with_mut(|friends| { + self.friend_list.mutate(|friends| { *friends = packet.friends.into_iter().map(|friend| (friend, UnsafeCell::new(None))).collect(); - ValueState::Mutated(()) }); } FriendOnlineStatusPacket::HEADER => { @@ -3932,9 +3933,8 @@ impl NetworkingSystem { #[cfg(feature = "debug")] pub fn clear_packet_history(&mut self) { - self.packet_history.with_mut(|buffer| { + self.packet_history.mutate(|buffer| { buffer.clear(); - ValueState::Mutated(()) }); } diff --git a/src/system/mod.rs b/korangar/src/system/mod.rs similarity index 100% rename from src/system/mod.rs rename to korangar/src/system/mod.rs diff --git a/src/system/timer.rs b/korangar/src/system/timer.rs similarity index 98% rename from src/system/timer.rs rename to korangar/src/system/timer.rs index 6f9e5080..4b77bda1 100644 --- a/src/system/timer.rs +++ b/korangar/src/system/timer.rs @@ -1,7 +1,7 @@ use std::time::Instant; use chrono::prelude::*; -use procedural::profile; +use korangar_procedural::profile; use crate::network::ClientTick; diff --git a/src/system/vulkan.rs b/korangar/src/system/vulkan.rs similarity index 100% rename from src/system/vulkan.rs rename to korangar/src/system/vulkan.rs diff --git a/src/world/effect/lookup.rs b/korangar/src/world/effect/lookup.rs similarity index 99% rename from src/world/effect/lookup.rs rename to korangar/src/world/effect/lookup.rs index c566e9db..3115ba21 100644 --- a/src/world/effect/lookup.rs +++ b/korangar/src/world/effect/lookup.rs @@ -1,5 +1,5 @@ -use procedural::PrototypeElement; -use ragnarok_procedural::ByteConvertable; +use korangar_procedural::PrototypeElement; +use ragnarok_bytes::ByteConvertable; #[derive(Clone, Debug, ByteConvertable, PrototypeElement)] #[numeric_type(u32)] diff --git a/src/world/effect/mod.rs b/korangar/src/world/effect/mod.rs similarity index 91% rename from src/world/effect/mod.rs rename to korangar/src/world/effect/mod.rs index f814c156..022aebd9 100644 --- a/src/world/effect/mod.rs +++ b/korangar/src/world/effect/mod.rs @@ -1,8 +1,8 @@ mod lookup; use cgmath::Vector3; -use procedural::{PrototypeElement, PrototypeWindow}; -use ragnarok_procedural::ByteConvertable; +use korangar_procedural::{PrototypeElement, PrototypeWindow}; +use ragnarok_bytes::ByteConvertable; #[cfg(feature = "debug")] use crate::graphics::{Camera, MarkerRenderer, Renderer}; diff --git a/src/world/entity/mod.rs b/korangar/src/world/entity/mod.rs similarity index 98% rename from src/world/entity/mod.rs rename to korangar/src/world/entity/mod.rs index e8824df4..e5423bb9 100644 --- a/src/world/entity/mod.rs +++ b/korangar/src/world/entity/mod.rs @@ -2,13 +2,17 @@ use std::sync::Arc; use cgmath::{Array, Vector2, Vector3, VectorSpace}; use derive_new::new; -use procedural::{profile, PrototypeElement, PrototypeWindow}; +use korangar_interface::windows::{PrototypeWindow, Window}; +use korangar_procedural::{profile, PrototypeElement, PrototypeWindow}; use vulkano::buffer::Subbuffer; #[cfg(feature = "debug")] use crate::graphics::MarkerRenderer; use crate::graphics::{Camera, DeferredRenderer, EntityRenderer, ModelVertex, Renderer}; -use crate::interface::{GameTheme, InterfaceSettings, PrototypeWindow, ScreenPosition, ScreenSize, Window, WindowCache}; +use crate::interface::application::InterfaceSettings; +use crate::interface::layout::{ScreenPosition, ScreenSize}; +use crate::interface::theme::GameTheme; +use crate::interface::windows::WindowCache; use crate::loaders::{ActionLoader, Actions, AnimationState, GameFileLoader, ScriptLoader, Sprite, SpriteLoader}; use crate::network::{AccountId, CharacterInformation, ClientTick, EntityData, EntityId, Sex, StatusType}; use crate::world::Map; @@ -1118,11 +1122,16 @@ impl Entity { } } -impl PrototypeWindow for Entity { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { +impl PrototypeWindow for Entity { + fn to_window( + &self, + window_cache: &WindowCache, + application: &InterfaceSettings, + available_space: ScreenSize, + ) -> Window { match self { - Entity::Player(player) => player.to_window(window_cache, interface_settings, available_space), - Entity::Npc(npc) => npc.to_window(window_cache, interface_settings, available_space), + Entity::Player(player) => player.to_window(window_cache, application, available_space), + Entity::Npc(npc) => npc.to_window(window_cache, application, available_space), } } } diff --git a/src/world/light/mod.rs b/korangar/src/world/light/mod.rs similarity index 92% rename from src/world/light/mod.rs rename to korangar/src/world/light/mod.rs index a1fd1eb5..cdd2f414 100644 --- a/src/world/light/mod.rs +++ b/korangar/src/world/light/mod.rs @@ -1,6 +1,6 @@ use cgmath::Vector3; -use procedural::{PrototypeElement, PrototypeWindow}; -use ragnarok_procedural::ByteConvertable; +use korangar_procedural::{PrototypeElement, PrototypeWindow}; +use ragnarok_bytes::ByteConvertable; use crate::graphics::*; #[cfg(feature = "debug")] diff --git a/src/world/map/mod.rs b/korangar/src/world/map/mod.rs similarity index 97% rename from src/world/map/mod.rs rename to korangar/src/world/map/mod.rs index fc67790e..d26776d9 100644 --- a/src/world/map/mod.rs +++ b/korangar/src/world/map/mod.rs @@ -1,13 +1,13 @@ mod tile; - use std::sync::Arc; use cgmath::{Array, EuclideanSpace, Matrix4, Point3, SquareMatrix, Vector2, Vector3}; use collision::{Aabb3, Frustum, Relation}; use derive_new::new; +use korangar_interface::windows::PrototypeWindow; +use korangar_procedural::profile; #[cfg(feature = "debug")] use option_ext::OptionExt; -use procedural::profile; use vulkano::buffer::Subbuffer; use vulkano::image::view::ImageView; @@ -15,8 +15,7 @@ pub use self::tile::Tile; #[cfg(feature = "debug")] use crate::debug::*; use crate::graphics::*; -#[cfg(feature = "debug")] -use crate::interface::PrototypeWindow; +use crate::interface::application::InterfaceSettings; #[cfg(feature = "debug")] use crate::loaders::MapData; use crate::loaders::{LightSettings, WaterSettings}; @@ -355,7 +354,7 @@ impl Map { } #[cfg(feature = "debug")] - pub fn to_prototype_window(&self) -> &dyn PrototypeWindow { + pub fn to_prototype_window(&self) -> &dyn PrototypeWindow { &self.map_data } @@ -371,7 +370,11 @@ impl Map { } #[cfg(feature = "debug")] - pub fn resolve_marker<'a>(&'a self, entities: &'a [Entity], marker_identifier: MarkerIdentifier) -> &dyn PrototypeWindow { + pub fn resolve_marker<'a>( + &'a self, + entities: &'a [Entity], + marker_identifier: MarkerIdentifier, + ) -> &dyn PrototypeWindow { match marker_identifier { MarkerIdentifier::Object(index) => &self.objects[index], MarkerIdentifier::LightSource(index) => &self.light_sources[index], diff --git a/src/world/map/tile.rs b/korangar/src/world/map/tile.rs similarity index 97% rename from src/world/map/tile.rs rename to korangar/src/world/map/tile.rs index cd16bac8..4fa61239 100644 --- a/src/world/map/tile.rs +++ b/korangar/src/world/map/tile.rs @@ -1,5 +1,4 @@ use ragnarok_bytes::{ByteStream, ConversionResult, FromBytes}; -use ragnarok_procedural::FromBytes; const NONE: u8 = 0b00000000; const WALKABLE: u8 = 0b00000001; diff --git a/src/world/mod.rs b/korangar/src/world/mod.rs similarity index 100% rename from src/world/mod.rs rename to korangar/src/world/mod.rs diff --git a/src/world/model/mod.rs b/korangar/src/world/model/mod.rs similarity index 98% rename from src/world/model/mod.rs rename to korangar/src/world/model/mod.rs index 27917af8..50ea7294 100644 --- a/src/world/model/mod.rs +++ b/korangar/src/world/model/mod.rs @@ -4,7 +4,7 @@ use std::ops::Mul; use cgmath::{Matrix4, Vector3}; use derive_new::new; -use procedural::PrototypeElement; +use korangar_procedural::PrototypeElement; pub use self::node::{BoundingBox, Node, OrientedBox}; use crate::graphics::{Camera, GeometryRenderer, Renderer, Transform}; diff --git a/src/world/model/node.rs b/korangar/src/world/model/node.rs similarity index 99% rename from src/world/model/node.rs rename to korangar/src/world/model/node.rs index 116418fa..4b2965d5 100644 --- a/src/world/model/node.rs +++ b/korangar/src/world/model/node.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use cgmath::{Array, Matrix4, SquareMatrix, Vector3, Vector4}; use derive_new::new; -use procedural::{profile, PrototypeElement}; +use korangar_procedural::{profile, PrototypeElement}; use vulkano::buffer::Subbuffer; use vulkano::image::view::ImageView; diff --git a/src/world/object/mod.rs b/korangar/src/world/object/mod.rs similarity index 96% rename from src/world/object/mod.rs rename to korangar/src/world/object/mod.rs index 07240d69..e2e109ee 100644 --- a/src/world/object/mod.rs +++ b/korangar/src/world/object/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use cgmath::Matrix4; use derive_new::new; -use procedural::{PrototypeElement, PrototypeWindow}; +use korangar_procedural::{PrototypeElement, PrototypeWindow}; use crate::graphics::*; use crate::network::ClientTick; diff --git a/src/world/sound/mod.rs b/korangar/src/world/sound/mod.rs similarity index 91% rename from src/world/sound/mod.rs rename to korangar/src/world/sound/mod.rs index fed179e0..2d2d30c5 100644 --- a/src/world/sound/mod.rs +++ b/korangar/src/world/sound/mod.rs @@ -1,6 +1,6 @@ use cgmath::Vector3; -use procedural::{PrototypeElement, PrototypeWindow}; -use ragnarok_procedural::ByteConvertable; +use korangar_procedural::{PrototypeElement, PrototypeWindow}; +use ragnarok_bytes::ByteConvertable; #[cfg(feature = "debug")] use crate::graphics::{Camera, MarkerRenderer, Renderer}; diff --git a/korangar_interface/Cargo.toml b/korangar_interface/Cargo.toml new file mode 100644 index 00000000..c4979f74 --- /dev/null +++ b/korangar_interface/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "korangar_interface" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = { workspace = true } +cgmath = { workspace = true, optional = true } +num = { workspace = true } +option-ext = "0.2.0" +korangar_procedural = { workspace = true, optional = true } +serde = { workspace = true, optional = true } + +[features] +cgmath = ["dep:cgmath"] +debug = ["korangar_procedural"] +serde = ["dep:serde"] diff --git a/korangar_interface/README.md b/korangar_interface/README.md new file mode 100644 index 00000000..2a7532eb --- /dev/null +++ b/korangar_interface/README.md @@ -0,0 +1,5 @@ +# Ragnarok Interface + +A crate that exposes a UI that can be used to display Ragnarok Online windows. + +All the `ragnarok-*` crates are meant to be independent from Korangar and have no dependencies to it, meaning they can be used for other Ragnarok Online related projects. diff --git a/korangar_interface/src/application.rs b/korangar_interface/src/application.rs new file mode 100644 index 00000000..cacc925c --- /dev/null +++ b/korangar_interface/src/application.rs @@ -0,0 +1,477 @@ +use std::rc::{Rc, Weak}; + +use crate::elements::{Element, ElementCell, WeakElementCell}; +use crate::theme::InterfaceTheme; + +pub trait Application: Sized + 'static { + type ThemeKind: Default; + type Theme: InterfaceTheme; + type Color: ColorTrait; + type Renderer: InterfaceRenderer; + type Size: SizeTrait; + type PartialSize: PartialSizeTrait; + type Position: PositionTrait; + type Clip: ClipTrait; + type CornerRadius: CornerRadiusTrait; + type FontSize: FontSizeTrait; + type Scaling: ScalingTrait; + type FontLoader: FontLoaderTrait; + type MouseInputMode: MouseInputModeTrait; + type Cache: WindowCache; + type DropResource; + type DropResult; + type CustomEvent; + + fn get_scaling(&self) -> Self::Scaling; + + fn get_theme(&self, kind: &Self::ThemeKind) -> &Self::Theme; +} + +pub trait MouseInputModeTrait +where + App: Application, +{ + fn is_none(&self) -> bool; + + fn is_self_dragged(&self, element: &dyn Element) -> bool; +} + +pub trait FontSizeTrait: Copy { + fn new(value: f32) -> Self; + + fn get_value(&self) -> f32; +} + +pub trait FontSizeTraitExt { + fn scaled(&self, scaling: impl ScalingTrait) -> Self; +} + +impl FontSizeTraitExt for T +where + T: FontSizeTrait, +{ + fn scaled(&self, scaling: impl ScalingTrait) -> Self { + Self::new(self.get_value() * scaling.get_factor()) + } +} + +pub trait FontLoaderTrait: Clone +where + App: Application, +{ + fn get_text_dimensions(&self, text: &str, font_size: App::FontSize, available_width: f32) -> App::Size; +} + +pub trait ScalingTrait: Copy { + fn get_factor(&self) -> f32; +} + +pub trait InterfaceRenderer +where + App: Application, +{ + type Target; + + fn get_text_dimensions(&self, text: &str, font_size: App::FontSize, available_width: f32) -> App::Size; + + fn render_rectangle( + &self, + render_target: &mut Self::Target, + position: App::Position, + size: App::Size, + clip: App::Clip, + corner_radius: App::CornerRadius, + color: App::Color, + ); + + fn render_text( + &self, + render_target: &mut Self::Target, + text: &str, + position: App::Position, + clip: App::Clip, + color: App::Color, + font_size: App::FontSize, + ) -> f32; + + fn render_checkbox( + &self, + render_target: &mut Self::Target, + position: App::Position, + size: App::Size, + clip: App::Clip, + color: App::Color, + checked: bool, + ); + + fn render_expand_arrow( + &self, + render_target: &mut Self::Target, + position: App::Position, + size: App::Size, + clip: App::Clip, + color: App::Color, + expanded: bool, + ); +} + +pub trait ColorTrait: Clone { + fn is_transparent(&self) -> bool; +} + +pub trait CornerRadiusTrait { + fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self; + + fn top_left(&self) -> f32; + + fn top_right(&self) -> f32; + + fn bottom_right(&self) -> f32; + + fn bottom_left(&self) -> f32; +} + +pub trait CornerRadiusTraitExt { + fn zero() -> Self; + + fn uniform(value: f32) -> Self; + + fn scaled(&self, scaling: impl ScalingTrait) -> Self; +} + +impl CornerRadiusTraitExt for T +where + T: CornerRadiusTrait, +{ + fn zero() -> Self { + Self::new(0.0, 0.0, 0.0, 0.0) + } + + fn uniform(value: f32) -> Self { + Self::new(value, value, value, value) + } + + fn scaled(&self, scaling: impl ScalingTrait) -> Self { + let factor = scaling.get_factor(); + Self::new( + self.top_left() * factor, + self.top_right() * factor, + self.bottom_right() * factor, + self.bottom_left() * factor, + ) + } +} + +// TODO: Rename +pub trait PositionTrait: Copy { + fn new(left: f32, top: f32) -> Self; + fn left(&self) -> f32; + fn top(&self) -> f32; +} + +pub trait PositionTraitExt { + fn zero() -> Self; + + fn only_left(left: f32) -> Self; + + fn only_top(top: f32) -> Self; + + fn from_size(size: impl SizeTrait) -> Self; + + fn offset(&self, size: impl SizeTrait) -> Self; + + fn combined(&self, other: Self) -> Self; + + fn remaining(&self, size: Size) -> Size + where + Size: SizeTrait; + + fn relative_to(&self, other: Self) -> Self; + + fn scaled(&self, scaling: impl ScalingTrait) -> Self; + + fn is_equal(&self, rhs: Self) -> bool; +} + +impl PositionTraitExt for T +where + T: PositionTrait, +{ + fn zero() -> Self { + Self::new(0.0, 0.0) + } + + fn only_left(left: f32) -> Self { + Self::new(left, 0.0) + } + + fn only_top(top: f32) -> Self { + Self::new(0.0, top) + } + + fn from_size(size: impl SizeTrait) -> Self { + Self::new(size.width(), size.height()) + } + + fn offset(&self, size: impl SizeTrait) -> Self { + Self::new(self.left() + size.width(), self.top() + size.height()) + } + + fn combined(&self, other: Self) -> Self { + Self::new(self.left() + other.left(), self.top() + other.top()) + } + + // TODO: Rename this given how it's used + fn remaining(&self, size: Size) -> Size + where + Size: SizeTrait, + { + Size::new(self.left() + size.width(), self.top() + size.height()) + } + + fn relative_to(&self, other: Self) -> Self { + Self::new(self.left() - other.left(), self.top() - other.top()) + } + + fn scaled(&self, scaling: impl ScalingTrait) -> Self { + let factor = scaling.get_factor(); + Self::new(self.left() * factor, self.top() * factor) + } + + fn is_equal(&self, rhs: Self) -> bool { + self.left() == rhs.left() && self.top() == rhs.top() + } +} + +pub trait SizeTrait: Copy { + fn new(width: f32, height: f32) -> Self; + fn width(&self) -> f32; + fn height(&self) -> f32; +} + +pub trait SizeTraitExt { + fn zero() -> Self; + + fn only_width(width: f32) -> Self; + + fn only_height(height: f32) -> Self; + + fn grow(&self, growth: Self) -> Self; + + fn shrink(&self, size: Self) -> Self; + + fn scaled(&self, scaling: impl ScalingTrait) -> Self; + + fn halved(&self) -> Self; + + fn doubled(&self) -> Self; + + fn is_equal(&self, rhs: Self) -> bool; +} + +impl SizeTraitExt for T +where + T: SizeTrait, +{ + fn zero() -> Self { + Self::new(0.0, 0.0) + } + + fn only_width(width: f32) -> Self { + Self::new(width, 0.0) + } + + fn only_height(height: f32) -> Self { + Self::new(0.0, height) + } + + fn grow(&self, size: Self) -> Self { + Self::new(self.width() + size.width(), self.height() + size.height()) + } + + fn shrink(&self, size: Self) -> Self { + Self::new(self.width() - size.width(), self.height() - size.height()) + } + + fn scaled(&self, scaling: impl ScalingTrait) -> Self { + let factor = scaling.get_factor(); + Self::new(self.width() * factor, self.height() * factor) + } + + fn halved(&self) -> Self { + Self::new(self.width() / 2.0, self.height() / 2.0) + } + + fn doubled(&self) -> Self { + Self::new(self.width() * 2.0, self.height() * 2.0) + } + + fn is_equal(&self, rhs: Self) -> bool { + self.width() == rhs.width() && self.height() == rhs.height() + } +} + +pub trait PartialSizeTrait: Copy { + fn new(width: f32, height: Option) -> Self; + fn width(&self) -> f32; + fn height(&self) -> Option; +} + +pub trait PartialSizeTraitExt { + fn finalize(self) -> Size + where + Size: SizeTrait; + + fn finalize_or(self, height: f32) -> Size + where + Size: SizeTrait; +} + +impl PartialSizeTraitExt for T +where + T: PartialSizeTrait, +{ + fn finalize(self) -> Size + where + Size: SizeTrait, + { + let width = self.width(); + let height = self.height().expect("element cannot have flexible height"); + + Size::new(width, height) + } + + fn finalize_or(self, height: f32) -> Size + where + Size: SizeTrait, + { + let width = self.width(); + let height = self.height().unwrap_or(height); + + Size::new(width, height) + } +} + +pub trait ClipTrait: Copy { + fn new(left: f32, top: f32, right: f32, bottom: f32) -> Self; + fn left(&self) -> f32; + fn right(&self) -> f32; + fn top(&self) -> f32; + fn bottom(&self) -> f32; +} + +pub trait WindowCache +where + App: Application, +{ + fn create() -> Self; + + fn register_window(&mut self, window_class: &str, position: App::Position, size: App::Size); + + fn update_position(&mut self, window_class: &str, position: App::Position); + + fn update_size(&mut self, window_class: &str, size: App::Size); + + fn get_window_state(&self, window_class: &str) -> Option<(App::Position, App::Size)>; +} + +pub struct FocusState +where + App: Application, +{ + focused_element: Option>, + focused_window: Option, + previous_hovered_element: Option>, + previous_hovered_window: Option, + previous_focused_element: Option>, + previous_focused_window: Option, +} + +impl Default for FocusState +where + App: Application, +{ + fn default() -> Self { + Self { + focused_element: Default::default(), + focused_window: Default::default(), + previous_hovered_element: Default::default(), + previous_hovered_window: Default::default(), + previous_focused_element: Default::default(), + previous_focused_window: Default::default(), + } + } +} + +impl FocusState +where + App: Application, +{ + pub fn remove_focus(&mut self) { + self.focused_element = None; + self.focused_window = None; + } + + pub fn set_focused_element(&mut self, element: Option>, window_index: usize) { + self.focused_element = element.as_ref().map(Rc::downgrade); + self.focused_window = Some(window_index); + } + + pub fn set_focused_window(&mut self, window_index: usize) { + self.focused_window = Some(window_index); + } + + pub fn get_focused_window(&self) -> Option { + self.focused_window.clone() + } + + pub fn update_focused_element(&mut self, element: Option>, window_index: usize) { + if let Some(element) = element { + self.focused_element = Some(Rc::downgrade(&element)); + self.focused_window = Some(window_index); + } + } + + pub fn get_focused_element(&self) -> Option<(ElementCell, usize)> { + let element = self.focused_element.clone(); + element.as_ref().and_then(Weak::upgrade).zip(self.focused_window) + } + + pub fn did_hovered_element_change(&self, hovered_element: &Option>) -> bool { + self.previous_hovered_element + .as_ref() + .zip(hovered_element.as_ref()) + .map(|(previous, current)| !Weak::ptr_eq(previous, &Rc::downgrade(current))) + .unwrap_or(self.previous_hovered_element.is_some() || hovered_element.is_some()) + } + + pub fn did_focused_element_change(&self) -> bool { + self.previous_focused_element + .as_ref() + .zip(self.focused_element.as_ref()) + .map(|(previous, current)| !Weak::ptr_eq(previous, current)) + .unwrap_or(self.previous_focused_element.is_some() || self.focused_element.is_some()) + } + + pub fn previous_hovered_window(&self) -> Option { + self.previous_hovered_window + } + + pub fn focused_window(&self) -> Option { + self.focused_window + } + + pub fn previous_focused_window(&self) -> Option { + self.previous_focused_window + } + + pub fn update(&mut self, hovered_element: &Option>, window_index: Option) -> Option> { + self.previous_hovered_element = hovered_element.as_ref().map(Rc::downgrade); + self.previous_hovered_window = window_index; + + self.previous_focused_element = self.focused_element.clone(); + self.previous_focused_window = self.focused_window; + + self.focused_element.clone().and_then(|weak_element| weak_element.upgrade()) + } +} diff --git a/src/interface/builder.rs b/korangar_interface/src/builder.rs similarity index 100% rename from src/interface/builder.rs rename to korangar_interface/src/builder.rs diff --git a/korangar_interface/src/elements/base.rs b/korangar_interface/src/elements/base.rs new file mode 100644 index 00000000..c2537bfb --- /dev/null +++ b/korangar_interface/src/elements/base.rs @@ -0,0 +1,354 @@ +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; + +use crate::application::{ + Application, ClipTrait, CornerRadiusTraitExt, FontSizeTraitExt, InterfaceRenderer, PartialSizeTraitExt, PositionTrait, + PositionTraitExt, SizeTrait, SizeTraitExt, +}; +use crate::event::{ChangeEvent, ClickAction, HoverInformation}; +use crate::layout::{PlacementResolver, SizeBound}; + +pub type ElementCell = Rc>>; +pub type WeakElementCell = Weak>>; + +pub trait ElementWrap +where + App: Application, +{ + fn wrap(self) -> ElementCell; +} + +impl ElementWrap for T +where + App: Application, + T: Element + Sized + 'static, +{ + fn wrap(self) -> ElementCell { + Rc::new(RefCell::new(self)) + } +} + +pub struct ElementRenderer<'a, App> +where + App: Application, +{ + pub render_target: &'a mut >::Target, + pub renderer: &'a App::Renderer, + pub application: &'a App, + pub position: App::Position, + pub size: App::Size, + pub clip: App::Clip, +} + +impl<'a, App> ElementRenderer<'a, App> +where + App: Application, +{ + pub fn get_position(&self) -> App::Position { + self.position + } + + pub fn get_text_dimensions(&self, text: &str, font_size: App::FontSize, available_width: f32) -> App::Size { + self.renderer + .get_text_dimensions(text, font_size.scaled(self.application.get_scaling()), available_width) + } + + pub fn set_scroll(&mut self, scroll: f32) { + self.position = App::Position::new(self.position.left(), self.position.top() - scroll); + } + + pub fn render_background(&mut self, corner_radius: App::CornerRadius, color: App::Color) { + self.renderer.render_rectangle( + self.render_target, + self.position, + self.size, + self.clip, + corner_radius.scaled(self.application.get_scaling()), + color, + ); + } + + pub fn render_rectangle(&mut self, position: App::Position, size: App::Size, corner_radius: App::CornerRadius, color: App::Color) { + self.renderer.render_rectangle( + self.render_target, + self.position.combined(position), + size, + self.clip, + corner_radius.scaled(self.application.get_scaling()), + color, + ); + } + + pub fn render_text(&mut self, text: &str, offset: App::Position, foreground_color: App::Color, font_size: App::FontSize) -> f32 { + self.renderer.render_text( + self.render_target, + text, + self.position.combined(offset.scaled(self.application.get_scaling())), + self.clip, + foreground_color, + font_size.scaled(self.application.get_scaling()), + ) + } + + pub fn render_checkbox(&mut self, offset: App::Position, size: App::Size, color: App::Color, checked: bool) { + self.renderer.render_checkbox( + self.render_target, + self.position.combined(offset.scaled(self.application.get_scaling())), + size.scaled(self.application.get_scaling()), + self.clip, + color, + checked, + ); + } + + pub fn render_expand_arrow(&mut self, offset: App::Position, size: App::Size, color: App::Color, expanded: bool) { + self.renderer.render_expand_arrow( + self.render_target, + self.position.combined(offset.scaled(self.application.get_scaling())), + size.scaled(self.application.get_scaling()), + self.clip, + color, + expanded, + ); + } + + pub fn render_element( + &mut self, + element: &dyn Element, + application: &App, + theme: &App::Theme, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, + mouse_mode: &App::MouseInputMode, + second_theme: bool, + ) { + element.render( + self.render_target, + self.renderer, + application, + theme, + self.position, + self.clip, + hovered_element, + focused_element, + mouse_mode, + second_theme, + ) + } +} + +pub struct ElementState +where + App: Application, +{ + pub cached_size: App::Size, + pub cached_position: App::Position, + pub self_element: Option>, + pub parent_element: Option>, + pub mouse_position: Cell, +} + +impl Default for ElementState +where + App: Application, +{ + fn default() -> Self { + Self { + cached_size: App::Size::zero(), + cached_position: App::Position::zero(), + self_element: None, + parent_element: None, + mouse_position: Cell::new(App::Position::zero()), + } + } +} + +impl ElementState +where + App: Application, +{ + pub fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { + self.self_element = Some(weak_self); + self.parent_element = weak_parent; + } + + pub fn resolve(&mut self, placement_resolver: &mut PlacementResolver, size_bound: &SizeBound) { + let (size, position) = placement_resolver.allocate(size_bound); + self.cached_size = size.finalize(); + self.cached_position = position; + } + + pub fn hovered_element(&self, mouse_position: App::Position) -> HoverInformation { + let absolute_position = mouse_position.relative_to(self.cached_position); + + if absolute_position.left() >= 0.0 + && absolute_position.top() >= 0.0 + && absolute_position.left() <= self.cached_size.width() + && absolute_position.top() <= self.cached_size.height() + { + self.mouse_position.replace(absolute_position); + return HoverInformation::Hovered; + } + + HoverInformation::Missed + } + + pub fn element_renderer<'a>( + &self, + render_target: &'a mut >::Target, + renderer: &'a App::Renderer, + application: &'a App, + parent_position: App::Position, + screen_clip: App::Clip, + ) -> ElementRenderer<'a, App> { + let position = parent_position.combined(self.cached_position); + let size = self.cached_size; + + let screen_clip = App::Clip::new( + screen_clip.left().max(position.left()), + screen_clip.top().max(position.top()), + screen_clip.right().min(position.left() + self.cached_size.width()), + screen_clip.bottom().min(position.top() + self.cached_size.height()), + ); + + ElementRenderer { + render_target, + renderer, + application, + position, + size, + clip: screen_clip, + } + } +} + +#[derive(Clone, Copy)] +pub struct Focus { + pub mode: FocusMode, + pub downwards: bool, +} + +impl Focus { + pub fn new(mode: FocusMode) -> Self { + Self { mode, downwards: false } + } + + pub fn downwards() -> Self { + Self { + mode: FocusMode::FocusNext, + downwards: true, + } + } + + pub fn to_downwards(self) -> Self { + Focus { + mode: self.mode, + downwards: true, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum FocusMode { + FocusNext, + FocusPrevious, +} + +impl From for FocusMode { + fn from(reverse: bool) -> Self { + match reverse { + true => Self::FocusPrevious, + false => Self::FocusNext, + } + } +} + +pub trait Element +where + App: Application, +{ + fn get_state(&self) -> &ElementState; + + fn get_state_mut(&mut self) -> &mut ElementState; + + fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option>) { + self.get_state_mut().link_back(weak_self, weak_parent); + } + + fn is_focusable(&self) -> bool { + true + } + + fn focus_next(&self, self_cell: ElementCell, _caller_cell: Option>, focus: Focus) -> Option> { + if focus.downwards { + return Some(self_cell); + } + + self.get_state().parent_element.as_ref().and_then(|parent_element| { + let parent_element = parent_element.upgrade().unwrap(); + let next_element = parent_element.borrow().focus_next(parent_element.clone(), Some(self_cell), focus); + next_element + }) + } + + fn restore_focus(&self, self_cell: ElementCell) -> Option> { + self.is_focusable().then_some(self_cell) + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver, application: &App, theme: &App::Theme); + + fn update(&mut self) -> Option { + None + } + + fn is_element_self(&self, element: Option<&dyn Element>) -> bool { + matches!(element, Some(reference) if std::ptr::eq(reference as *const _ as *const (), self as *const _ as *const ())) + } + + fn hovered_element(&self, _mouse_position: App::Position, _mouse_mode: &App::MouseInputMode) -> HoverInformation { + HoverInformation::Missed + } + + fn left_click(&mut self, _update: &mut bool) -> Vec> { + Vec::new() + } + + fn right_click(&mut self, _update: &mut bool) -> Vec> { + Vec::new() + } + + fn drag(&mut self, _mouse_delta: App::Position) -> Option { + None + } + + fn input_character(&mut self, _character: char) -> Vec> { + Vec::new() + } + + fn drop_resource(&mut self, drop_resource: App::DropResource) -> Option { + let _ = drop_resource; + None + } + + fn scroll(&mut self, delta: f32) -> Option { + self.get_state() + .parent_element + .as_ref() + .and_then(|weak_pointer| weak_pointer.upgrade()) + .and_then(|element| (*element).borrow_mut().scroll(delta)) + } + + fn render( + &self, + render_target: &mut >::Target, + render: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, + mouse_mode: &App::MouseInputMode, + second_theme: bool, + ); +} diff --git a/src/interface/elements/buttons/close/builder.rs b/korangar_interface/src/elements/buttons/close/builder.rs similarity index 80% rename from src/interface/elements/buttons/close/builder.rs rename to korangar_interface/src/elements/buttons/close/builder.rs index 447ca990..802d4bed 100644 --- a/src/interface/elements/buttons/close/builder.rs +++ b/korangar_interface/src/elements/buttons/close/builder.rs @@ -1,4 +1,5 @@ use super::CloseButton; +use crate::application::Application; /// Type state [`CloseButton`] builder. This builder utilizes the type system to /// prevent calling the same method multiple times and calling @@ -12,7 +13,10 @@ impl CloseButtonBuilder { } /// Take the builder and turn it into a [`CloseButton`]. - pub fn build(self) -> CloseButton { + pub fn build(self) -> CloseButton + where + App: Application, + { CloseButton { state: Default::default() } } } diff --git a/korangar_interface/src/elements/buttons/close/mod.rs b/korangar_interface/src/elements/buttons/close/mod.rs new file mode 100644 index 00000000..e98cc2d2 --- /dev/null +++ b/korangar_interface/src/elements/buttons/close/mod.rs @@ -0,0 +1,81 @@ +mod builder; + +pub use self::builder::CloseButtonBuilder; +use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait, PartialSizeTraitExt}; +use crate::elements::{Element, ElementState}; +use crate::event::{ClickAction, HoverInformation}; +use crate::layout::PlacementResolver; +use crate::theme::{CloseButtonTheme, InterfaceTheme}; + +pub struct CloseButton +where + App: Application, +{ + state: ElementState, +} + +impl Element for CloseButton +where + App: Application, +{ + fn get_state(&self) -> &ElementState { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _application: &App, theme: &App::Theme) { + let (size, position) = placement_resolver.allocate_right(&theme.close_button().size_bound()); + self.state.cached_size = size.finalize(); + self.state.cached_position = position; + } + + fn is_focusable(&self) -> bool { + false + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation { + match mouse_mode.is_none() { + true => self.state.hovered_element(mouse_position), + false => HoverInformation::Missed, + } + } + + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { + vec![ClickAction::CloseWindow] + } + + fn render( + &self, + render_target: &mut >::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { + true => theme.close_button().hovered_background_color(), + false => theme.close_button().background_color(), + }; + + renderer.render_background(theme.close_button().corner_radius(), background_color); + + renderer.render_text( + "X", + theme.close_button().text_offset(), + theme.close_button().foreground_color(), + theme.close_button().font_size(), + ); + } +} diff --git a/korangar_interface/src/elements/buttons/default/builder.rs b/korangar_interface/src/elements/buttons/default/builder.rs new file mode 100644 index 00000000..de3675e0 --- /dev/null +++ b/korangar_interface/src/elements/buttons/default/builder.rs @@ -0,0 +1,160 @@ +use std::marker::PhantomData; + +use super::Button; +use crate::application::Application; +use crate::builder::{Set, Unset}; +use crate::layout::DimensionBound; +use crate::{ColorSelector, ElementEvent, Selector}; + +/// Type state [`Button`] builder. This builder utilizes the type system to +/// prevent calling the same method multiple times and calling +/// [`build`](Self::build) before the mandatory methods have been called. +#[must_use = "`build` needs to be called"] +pub struct ButtonBuilder +where + App: Application, +{ + text: Text, + event: Event, + disabled_selector: Option, + foreground_color: Option>, + background_color: Option>, + width_bound: DimensionBound, + marker: PhantomData<(Disabled, Foreground, Background, Width)>, +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn new() -> Self { + Self { + text: Unset, + event: Unset, + disabled_selector: None, + foreground_color: None, + background_color: None, + width_bound: DimensionBound::RELATIVE_ONE_HUNDRED, + marker: PhantomData, + } + } +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn with_text + 'static>( + self, + text: Text, + ) -> ButtonBuilder { + ButtonBuilder { text, ..self } + } +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn with_event + 'static>( + self, + event: Event, + ) -> ButtonBuilder { + ButtonBuilder { event, ..self } + } +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn with_disabled_selector( + self, + selector: impl Fn() -> bool + 'static, + ) -> ButtonBuilder { + ButtonBuilder { + disabled_selector: Some(Box::new(selector)), + marker: PhantomData, + ..self + } + } +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn with_foreground_color( + self, + color_selector: impl Fn(&App::Theme) -> App::Color + 'static, + ) -> ButtonBuilder { + ButtonBuilder { + foreground_color: Some(Box::new(color_selector)), + marker: PhantomData, + ..self + } + } +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn with_background_color( + self, + color_selector: impl Fn(&App::Theme) -> App::Color + 'static, + ) -> ButtonBuilder { + ButtonBuilder { + background_color: Some(Box::new(color_selector)), + marker: PhantomData, + ..self + } + } +} + +impl ButtonBuilder +where + App: Application, +{ + pub fn with_width_bound(self, width_bound: DimensionBound) -> ButtonBuilder { + ButtonBuilder { + width_bound, + marker: PhantomData, + ..self + } + } +} + +impl ButtonBuilder +where + App: Application, + Text: AsRef + 'static, + Event: ElementEvent + 'static, +{ + /// Take the builder and turn it into a [`Button`]. + /// + /// NOTE: This method is only available if [`with_text`](Self::with_text) + /// and [`with_event`](Self::with_event) have been called on + /// the builder. + pub fn build(self) -> Button { + let Self { + text, + event, + disabled_selector, + foreground_color, + background_color, + width_bound, + .. + } = self; + + Button { + text, + event, + disabled_selector, + foreground_color, + background_color, + width_bound, + state: Default::default(), + } + } +} diff --git a/korangar_interface/src/elements/buttons/default/mod.rs b/korangar_interface/src/elements/buttons/default/mod.rs new file mode 100644 index 00000000..00bf012c --- /dev/null +++ b/korangar_interface/src/elements/buttons/default/mod.rs @@ -0,0 +1,117 @@ +mod builder; + +pub use self::builder::ButtonBuilder; +use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait}; +use crate::elements::{Element, ElementState}; +use crate::event::{ClickAction, HoverInformation}; +use crate::layout::{DimensionBound, PlacementResolver}; +use crate::theme::{ButtonTheme, InterfaceTheme}; +use crate::{ColorSelector, ElementEvent, Selector}; + +pub struct Button +where + App: Application, + Text: AsRef + 'static, + Event: ElementEvent + 'static, +{ + text: Text, + event: Event, + disabled_selector: Option, + foreground_color: Option>, + background_color: Option>, + width_bound: DimensionBound, + state: ElementState, +} + +impl Button +where + App: Application, + Text: AsRef + 'static, + Event: ElementEvent + 'static, +{ + fn is_disabled(&self) -> bool { + self.disabled_selector.as_ref().map(|selector| !selector()).unwrap_or(false) + } +} + +impl Element for Button +where + App: Application, + Text: AsRef + 'static, + Event: ElementEvent, +{ + fn get_state(&self) -> &ElementState { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState { + &mut self.state + } + + fn is_focusable(&self) -> bool { + !self.is_disabled() + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _application: &App, theme: &App::Theme) { + let size_bound = self.width_bound.add_height(theme.button().height_bound()); + self.state.resolve(placement_resolver, &size_bound); + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation { + match mouse_mode.is_none() { + true => self.state.hovered_element(mouse_position), + false => HoverInformation::Missed, + } + } + + fn left_click(&mut self, _force_update: &mut bool) -> Vec> { + match self.is_disabled() { + true => Vec::new(), + false => self.event.trigger(), + } + } + + fn render( + &self, + render_target: &mut >::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element>, + focused_element: Option<&dyn Element>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let disabled = self.is_disabled(); + let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { + _ if disabled => theme.button().disabled_background_color(), + true => theme.button().hovered_background_color(), + false if self.background_color.is_some() => (self.background_color.as_ref().unwrap())(theme), + false => theme.button().background_color(), + }; + + renderer.render_background(theme.button().corner_radius(), background_color); + + let foreground_color = if disabled { + theme.button().disabled_foreground_color() + } else { + self.foreground_color + .as_ref() + .map(|closure| closure(theme)) + .unwrap_or(theme.button().foreground_color()) + }; + + renderer.render_text( + self.text.as_ref(), + theme.button().text_offset(), + foreground_color, + theme.button().font_size(), + ); + } +} diff --git a/src/interface/elements/buttons/drag/builder.rs b/korangar_interface/src/elements/buttons/drag/builder.rs similarity index 73% rename from src/interface/elements/buttons/drag/builder.rs rename to korangar_interface/src/elements/buttons/drag/builder.rs index 7f89d134..d7fbd8c2 100644 --- a/src/interface/elements/buttons/drag/builder.rs +++ b/korangar_interface/src/elements/buttons/drag/builder.rs @@ -1,14 +1,15 @@ use super::DragButton; -use crate::interface::builder::Unset; -use crate::interface::*; +use crate::application::Application; +use crate::builder::Unset; +use crate::layout::DimensionBound; /// Type state [`DragButton`] builder. This builder utilizes the type system to /// prevent calling the same method multiple times and calling /// [`build`](Self::build) before the mandatory methods have been called. #[must_use = "`build` needs to be called"] -pub struct DragButtonBuilder { - title: TITLE, - width_bound: WIDTH, +pub struct DragButtonBuilder { + title: Title, + width_bound: Width, } impl DragButtonBuilder { @@ -20,8 +21,8 @@ impl DragButtonBuilder { } } -impl DragButtonBuilder { - pub fn with_title(self, title: impl Into) -> DragButtonBuilder { +impl DragButtonBuilder { + pub fn with_title(self, title: impl Into) -> DragButtonBuilder { DragButtonBuilder { title: title.into(), ..self @@ -29,8 +30,8 @@ impl DragButtonBuilder { } } -impl DragButtonBuilder<TITLE, Unset> { - pub fn with_width_bound(self, width_bound: DimensionBound) -> DragButtonBuilder<TITLE, DimensionBound> { +impl<Title> DragButtonBuilder<Title, Unset> { + pub fn with_width_bound(self, width_bound: DimensionBound) -> DragButtonBuilder<Title, DimensionBound> { DragButtonBuilder { width_bound, ..self } } } @@ -41,7 +42,10 @@ impl DragButtonBuilder<String, DimensionBound> { /// NOTE: This method is only available if [`with_title`](Self::with_title) /// and [`with_width_bound`](Self::with_width_bound) have been called on /// the builder. - pub fn build(self) -> DragButton { + pub fn build<App>(self) -> DragButton<App> + where + App: Application, + { let Self { title, width_bound } = self; DragButton { diff --git a/korangar_interface/src/elements/buttons/drag/mod.rs b/korangar_interface/src/elements/buttons/drag/mod.rs new file mode 100644 index 00000000..55cf53b7 --- /dev/null +++ b/korangar_interface/src/elements/buttons/drag/mod.rs @@ -0,0 +1,83 @@ +mod builder; + +pub use self::builder::DragButtonBuilder; +use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait}; +use crate::elements::{Element, ElementState}; +use crate::event::{ClickAction, HoverInformation}; +use crate::layout::{DimensionBound, PlacementResolver}; +use crate::theme::{InterfaceTheme, WindowTheme}; + +pub struct DragButton<App> +where + App: Application, +{ + title: String, + width_bound: DimensionBound, + state: ElementState<App>, +} + +impl<App> Element<App> for DragButton<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + let size_bound = self.width_bound.add_height(theme.window().title_height()); + + self.state.resolve(placement_resolver, &size_bound); + } + + fn is_focusable(&self) -> bool { + false + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + if mouse_mode.is_none() { + self.state.hovered_element(mouse_position) + } else if mouse_mode.is_self_dragged(self) { + HoverInformation::Hovered + } else { + HoverInformation::Missed + } + } + + fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction<App>> { + vec![ClickAction::MoveInterface] + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + _focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + if self.is_element_self(hovered_element) { + renderer.render_background(theme.window().title_corner_radius(), theme.window().title_background_color()); + } + + renderer.render_text( + &self.title, + theme.window().text_offset(), + theme.window().foreground_color(), + theme.window().font_size(), + ); + } +} diff --git a/src/interface/elements/buttons/mod.rs b/korangar_interface/src/elements/buttons/mod.rs similarity index 100% rename from src/interface/elements/buttons/mod.rs rename to korangar_interface/src/elements/buttons/mod.rs diff --git a/korangar_interface/src/elements/buttons/state/builder.rs b/korangar_interface/src/elements/buttons/state/builder.rs new file mode 100644 index 00000000..58152d38 --- /dev/null +++ b/korangar_interface/src/elements/buttons/state/builder.rs @@ -0,0 +1,137 @@ +use std::marker::PhantomData; + +use super::StateButton; +use crate::application::Application; +use crate::builder::{Set, Unset}; +use crate::layout::DimensionBound; +use crate::state::Remote; +use crate::ElementEvent; + +/// Type state [`StateButton`] builder. This builder utilizes the type system to +/// prevent calling the same method multiple times and calling +/// [`build`](Self::build) before the mandatory methods have been called. +#[must_use = "`build` needs to be called"] +pub struct StateButtonBuilder<App, Text, Event, State, Background, Width> +where + App: Application, +{ + text: Text, + event: Event, + remote: State, + transparent_background: bool, + width_bound: DimensionBound, + marker: PhantomData<(App, State, Background, Width)>, +} + +impl<App> StateButtonBuilder<App, Unset, Unset, Unset, Unset, Unset> +where + App: Application, +{ + pub fn new() -> Self { + Self { + text: Unset, + event: Unset, + remote: Unset, + transparent_background: false, + width_bound: DimensionBound::RELATIVE_ONE_HUNDRED, + marker: PhantomData, + } + } +} + +impl<App, Event, State, Background, Width> StateButtonBuilder<App, Unset, Event, State, Background, Width> +where + App: Application, +{ + pub fn with_text<Text: AsRef<str> + 'static>(self, text: Text) -> StateButtonBuilder<App, Text, Event, State, Background, Width> { + StateButtonBuilder { text, ..self } + } +} + +impl<App, Text, State, Background, Width> StateButtonBuilder<App, Text, Unset, State, Background, Width> +where + App: Application, +{ + pub fn with_event<Event: ElementEvent<App> + 'static>( + self, + event: Event, + ) -> StateButtonBuilder<App, Text, Event, State, Background, Width> { + StateButtonBuilder { event, ..self } + } +} + +impl<App, Text, Event, Background, Width> StateButtonBuilder<App, Text, Event, Unset, Background, Width> +where + App: Application, +{ + pub fn with_remote<State>(self, remote: State) -> StateButtonBuilder<App, Text, Event, State, Background, Width> + where + State: Remote<bool> + 'static, + { + StateButtonBuilder { + remote, + marker: PhantomData, + ..self + } + } +} + +impl<App, Text, Event, State, Width> StateButtonBuilder<App, Text, Event, State, Unset, Width> +where + App: Application, +{ + pub fn with_transparent_background(self) -> StateButtonBuilder<App, Text, Event, State, Set, Width> { + StateButtonBuilder { + transparent_background: true, + marker: PhantomData, + ..self + } + } +} + +impl<App, Text, Event, State, Background> StateButtonBuilder<App, Text, Event, State, Background, Unset> +where + App: Application, +{ + pub fn with_width_bound(self, width_bound: DimensionBound) -> StateButtonBuilder<App, Text, Event, State, Background, Set> { + StateButtonBuilder { + width_bound, + marker: PhantomData, + ..self + } + } +} + +impl<App, Text, Event, State, Background, Width> StateButtonBuilder<App, Text, Event, State, Background, Width> +where + App: Application, + Text: AsRef<str> + 'static, + Event: ElementEvent<App> + 'static, + State: Remote<bool> + 'static, +{ + /// Take the builder and turn it into a [`StateButton`]. + /// + /// NOTE: This method is only available if [`with_text`](Self::with_text), + /// [`with_event`](Self::with_event), and + /// [`with_selector`](Self::with_selector) have been called on + /// the builder. + pub fn build(self) -> StateButton<App, Text, Event, State> { + let Self { + text, + event, + remote, + transparent_background, + width_bound, + .. + } = self; + + StateButton { + text, + event, + remote, + transparent_background, + width_bound, + state: Default::default(), + } + } +} diff --git a/korangar_interface/src/elements/buttons/state/mod.rs b/korangar_interface/src/elements/buttons/state/mod.rs new file mode 100644 index 00000000..070e8472 --- /dev/null +++ b/korangar_interface/src/elements/buttons/state/mod.rs @@ -0,0 +1,110 @@ +mod builder; + +pub use self::builder::StateButtonBuilder; +use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait}; +use crate::elements::{Element, ElementState}; +use crate::event::{ChangeEvent, ClickAction, HoverInformation}; +use crate::layout::{DimensionBound, PlacementResolver}; +use crate::state::{Remote, RemoteClone}; +use crate::theme::{ButtonTheme, InterfaceTheme}; +use crate::ElementEvent; + +// FIX: State button won't redraw just because the state changes +pub struct StateButton<App, Text, Event, State> +where + App: Application, + Text: AsRef<str> + 'static, + Event: ElementEvent<App> + 'static, + State: Remote<bool>, +{ + text: Text, + event: Event, + remote: State, + width_bound: DimensionBound, + transparent_background: bool, + state: ElementState<App>, +} + +impl<App, Text, Event, State> Element<App> for StateButton<App, Text, Event, State> +where + App: Application, + Text: AsRef<str> + 'static, + Event: ElementEvent<App> + 'static, + State: Remote<bool>, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + let size_bound = self.width_bound.add_height(theme.button().height_bound()); + self.state.resolve(placement_resolver, &size_bound); + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + match mouse_mode.is_none() { + true => self.state.hovered_element(mouse_position), + false => HoverInformation::Missed, + } + } + + fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction<App>> { + self.event.trigger() + } + + fn update(&mut self) -> Option<ChangeEvent> { + self.remote.consume_changed().then_some(ChangeEvent::RENDER_WINDOW) + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let highlighted = self.is_element_self(hovered_element) || self.is_element_self(focused_element); + + if !self.transparent_background { + let background_color = match highlighted { + true => theme.button().hovered_background_color(), + false => theme.button().background_color(), + }; + + renderer.render_background(theme.button().corner_radius(), background_color); + } + + let foreground_color = match self.transparent_background && highlighted { + true => theme.button().hovered_foreground_color(), + false => theme.button().foreground_color(), + }; + + renderer.render_checkbox( + theme.button().icon_offset(), + theme.button().icon_size(), + foreground_color.clone(), + self.remote.cloned(), + ); + + renderer.render_text( + self.text.as_ref(), + theme.button().icon_text_offset(), + foreground_color, + theme.button().font_size(), + ); + } +} diff --git a/korangar_interface/src/elements/containers/default.rs b/korangar_interface/src/elements/containers/default.rs new file mode 100644 index 00000000..3891523b --- /dev/null +++ b/korangar_interface/src/elements/containers/default.rs @@ -0,0 +1,109 @@ +use std::cell::RefCell; +use std::rc::Weak; + +use super::ContainerState; +use crate::application::{Application, InterfaceRenderer, SizeTraitExt}; +use crate::elements::{Element, ElementCell, ElementState, Focus}; +use crate::event::{ChangeEvent, HoverInformation}; +use crate::layout::{Dimension, PlacementResolver, SizeBound}; + +pub struct Container<App> +where + App: Application, +{ + size_bound: Option<SizeBound>, + border_size: Option<App::Size>, + state: ContainerState<App>, +} + +impl<App> Container<App> +where + App: Application, +{ + pub fn new(elements: Vec<ElementCell<App>>) -> Self { + Self { + state: ContainerState::new(elements), + border_size: None, + size_bound: None, + } + } + + pub fn with_size(mut self, size_bound: SizeBound) -> Self { + self.size_bound = Some(size_bound); + self + } +} + +impl<App> Element<App> for Container<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state.state + } + + fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element<App>>>, weak_parent: Option<Weak<RefCell<dyn Element<App>>>>) { + self.state.link_back(weak_self, weak_parent); + } + + fn is_focusable(&self) -> bool { + self.state.is_focusable::<false>() + } + + fn focus_next(&self, self_cell: ElementCell<App>, caller_cell: Option<ElementCell<App>>, focus: Focus) -> Option<ElementCell<App>> { + self.state.focus_next::<false>(self_cell, caller_cell, focus) + } + + fn restore_focus(&self, self_cell: ElementCell<App>) -> Option<ElementCell<App>> { + self.state.restore_focus(self_cell) + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, application: &App, theme: &App::Theme) { + let default_size_bound = SizeBound::only_height(Dimension::Flexible); + let size_bound = self.size_bound.as_ref().unwrap_or(&default_size_bound); + let border = self.border_size.unwrap_or(App::Size::zero()); + + self.state.resolve(placement_resolver, application, theme, size_bound, border); + } + + fn update(&mut self) -> Option<ChangeEvent> { + self.state.update() + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + self.state.hovered_element(mouse_position, mouse_mode, false) + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + mouse_mode: &App::MouseInputMode, + second_theme: bool, + ) { + let mut renderer = self + .state + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + self.state.render( + &mut renderer, + application, + theme, + hovered_element, + focused_element, + mouse_mode, + second_theme, + ); + } +} diff --git a/korangar_interface/src/elements/containers/expandable.rs b/korangar_interface/src/elements/containers/expandable.rs new file mode 100644 index 00000000..90418715 --- /dev/null +++ b/korangar_interface/src/elements/containers/expandable.rs @@ -0,0 +1,225 @@ +use std::cell::RefCell; +use std::rc::Weak; + +use super::ContainerState; +use crate::application::{ + Application, InterfaceRenderer, MouseInputModeTrait, PartialSizeTrait, PartialSizeTraitExt, PositionTrait, PositionTraitExt, + ScalingTrait, SizeTrait, SizeTraitExt, +}; +use crate::elements::{Element, ElementCell, ElementState, Focus}; +use crate::event::{ChangeEvent, ClickAction, HoverInformation}; +use crate::layout::{Dimension, PlacementResolver, SizeBound}; +use crate::theme::{ExpandableTheme, InterfaceTheme}; + +pub struct Expandable<App> +where + App: Application, +{ + display: String, + expanded: bool, + open_size_bound: SizeBound, + closed_size_bound: SizeBound, + cached_closed_size: App::Size, + state: ContainerState<App>, +} + +impl<App> Expandable<App> +where + App: Application, +{ + pub fn new(display: String, elements: Vec<ElementCell<App>>, expanded: bool) -> Self { + let state = ContainerState::new(elements); + + Self { + display, + expanded, + open_size_bound: SizeBound::only_height(Dimension::Flexible), + closed_size_bound: SizeBound::only_height(Dimension::Absolute(18.0)), + cached_closed_size: App::Size::zero(), + state, + } + } +} + +impl<App> Element<App> for Expandable<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state.state + } + + fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element<App>>>, weak_parent: Option<Weak<RefCell<dyn Element<App>>>>) { + self.state.link_back(weak_self, weak_parent); + } + + fn is_focusable(&self) -> bool { + self.state.is_focusable::<true>() + } + + fn focus_next(&self, self_cell: ElementCell<App>, caller_cell: Option<ElementCell<App>>, focus: Focus) -> Option<ElementCell<App>> { + // TODO: fix collapsed elements being focusable + self.state.focus_next::<true>(self_cell, caller_cell, focus) + } + + fn restore_focus(&self, self_cell: ElementCell<App>) -> Option<ElementCell<App>> { + self.state.restore_focus(self_cell) + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, application: &App, theme: &App::Theme) { + let closed_size = self + .closed_size_bound + .resolve_element::<App::PartialSize>( + placement_resolver.get_available(), + placement_resolver.get_remaining(), + &placement_resolver.get_parent_limits(), + application.get_scaling(), + ) + .finalize::<App::Size>(); + + let size_bound = match self.expanded && !self.state.elements.is_empty() { + true => &self.open_size_bound, + false => &self.closed_size_bound, + }; + + let screen_position = App::Position::only_top(closed_size.height()) + .combined(theme.expandable().element_offset()) + .scaled(application.get_scaling()); + + let (mut inner_placement_resolver, mut size, position) = + placement_resolver.derive(size_bound, screen_position, theme.expandable().border_size()); + let parent_limits = inner_placement_resolver.get_parent_limits(); + + if self.expanded && !self.state.elements.is_empty() { + inner_placement_resolver.set_gaps(theme.expandable().gaps()); + + self.state + .elements + .iter_mut() + .for_each(|element| element.borrow_mut().resolve(&mut inner_placement_resolver, application, theme)); + + if self.open_size_bound.height.is_flexible() { + let final_height = inner_placement_resolver.final_height() + + closed_size.height() + + theme.expandable().element_offset().top() * application.get_scaling().get_factor() + + theme.expandable().border_size().height() * application.get_scaling().get_factor() * 2.0; + + let final_height = self.open_size_bound.validated_height( + final_height, + placement_resolver.get_available().height(), + placement_resolver.get_available().height(), + &parent_limits, + application.get_scaling(), + ); + + size = App::PartialSize::new(size.width(), Some(final_height)); + placement_resolver.register_height(final_height); + } + } + + self.cached_closed_size = closed_size; + self.state.state.cached_size = size.finalize(); + self.state.state.cached_position = position; + } + + fn update(&mut self) -> Option<ChangeEvent> { + if !self.expanded || self.state.elements.is_empty() { + return None; + } + + self.state.update() + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + let absolute_position = mouse_position.relative_to(self.state.state.cached_position); + + if absolute_position.left() >= 0.0 + && absolute_position.top() >= 0.0 + && absolute_position.left() <= self.state.state.cached_size.width() + && absolute_position.top() <= self.state.state.cached_size.height() + { + if self.expanded && !self.state.elements.is_empty() { + for element in &self.state.elements { + match element.borrow().hovered_element(absolute_position, mouse_mode) { + HoverInformation::Hovered => return HoverInformation::Element(element.clone()), + HoverInformation::Missed => {} + hover_information => return hover_information, + } + } + } + + if mouse_mode.is_none() { + return HoverInformation::Hovered; + } + } + + HoverInformation::Missed + } + + fn left_click(&mut self, force_update: &mut bool) -> Vec<ClickAction<App>> { + self.expanded = !self.expanded; + *force_update = true; + Vec::new() + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + mouse_mode: &App::MouseInputMode, + second_theme: bool, + ) { + let mut renderer = self + .state + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let background_color = match second_theme { + true => theme.expandable().second_background_color(), + false => theme.expandable().background_color(), + }; + + renderer.render_background(theme.expandable().corner_radius(), background_color); + + renderer.render_expand_arrow( + theme.expandable().icon_offset(), + theme.expandable().icon_size(), + theme.expandable().foreground_color(), + self.expanded, + ); + + let foreground_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { + true => theme.expandable().hovered_foreground_color(), + false => theme.expandable().foreground_color(), + }; + + renderer.render_text( + &self.display, + theme.expandable().text_offset(), + foreground_color, + theme.expandable().font_size(), + ); + + if self.expanded && !self.state.elements.is_empty() { + self.state.render( + &mut renderer, + application, + theme, + hovered_element, + focused_element, + mouse_mode, + !second_theme, + ); + } + } +} diff --git a/src/interface/elements/containers/mod.rs b/korangar_interface/src/elements/containers/mod.rs similarity index 76% rename from src/interface/elements/containers/mod.rs rename to korangar_interface/src/elements/containers/mod.rs index e8747f35..983fdc72 100644 --- a/src/interface/elements/containers/mod.rs +++ b/korangar_interface/src/elements/containers/mod.rs @@ -1,48 +1,46 @@ -mod character; mod default; -mod dialog; -mod equipment; mod expandable; -mod friends; -mod hotbar; -mod inventory; -#[cfg(feature = "debug")] -mod packet; mod scroll; -mod skill_tree; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::ops::Add; -use std::rc::Weak; +use std::rc::{Rc, Weak}; -use derive_new::new; - -pub use self::character::CharacterPreview; pub use self::default::Container; -pub use self::dialog::{DialogContainer, DialogElement}; -pub use self::equipment::EquipmentContainer; pub use self::expandable::Expandable; -pub use self::friends::FriendView; -pub use self::hotbar::HotbarContainer; -pub use self::inventory::InventoryContainer; -#[cfg(feature = "debug")] -pub use self::packet::{PacketEntry, PacketView}; pub use self::scroll::ScrollView; -pub use self::skill_tree::SkillTreeContainer; -use crate::input::MouseInputMode; -use crate::interface::*; - -#[derive(new)] -pub struct ContainerState { - elements: Vec<ElementCell>, - #[new(default)] - state: ElementState, - #[new(default)] - focus_cache: Cell<Option<usize>>, +use super::{Element, ElementCell, ElementRenderer, ElementState, Focus, FocusMode}; +use crate::application::{Application, PartialSizeTrait, PartialSizeTraitExt, PositionTrait, PositionTraitExt, SizeTrait}; +use crate::event::{ChangeEvent, HoverInformation}; +use crate::layout::{PlacementResolver, SizeBound}; + +pub struct ContainerState<App> +where + App: Application, +{ + pub elements: Vec<ElementCell<App>>, + pub state: ElementState<App>, + pub focus_cache: Cell<Option<usize>>, +} + +impl<App> ContainerState<App> +where + App: Application, +{ + pub fn new(elements: Vec<ElementCell<App>>) -> Self { + Self { + elements, + state: Default::default(), + focus_cache: Default::default(), + } + } } -impl ContainerState { - pub fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element>>, weak_parent: Option<Weak<RefCell<dyn Element>>>) { +impl<App> ContainerState<App> +where + App: Application, +{ + pub fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element<App>>>, weak_parent: Option<Weak<RefCell<dyn Element<App>>>>) { self.elements.iter().for_each(|element| { let weak_element = Rc::downgrade(element); element.borrow_mut().link_back(weak_element, Some(weak_self.clone())); @@ -52,35 +50,34 @@ impl ContainerState { pub fn resolve( &mut self, - placement_resolver: &mut PlacementResolver, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, + placement_resolver: &mut PlacementResolver<App>, + application: &App, + theme: &App::Theme, size_bound: &SizeBound, - border: ScreenSize, + border: App::Size, ) -> f32 { - let (mut inner_placement_resolver, mut size, position) = placement_resolver.derive(size_bound, ScreenPosition::default(), border); + let (mut inner_placement_resolver, mut size, position) = placement_resolver.derive(size_bound, App::Position::zero(), border); let parent_limits = inner_placement_resolver.get_parent_limits(); // TODO: add ability to pass this in (by calling .with_gaps(..) on the // container) inner_placement_resolver.set_gaps(Size::new(5.0, 3.0)); - self.elements.iter_mut().for_each(|element| { - element - .borrow_mut() - .resolve(&mut inner_placement_resolver, interface_settings, theme) - }); + self.elements + .iter_mut() + .for_each(|element| element.borrow_mut().resolve(&mut inner_placement_resolver, application, theme)); let final_height = inner_placement_resolver.final_height(); if size_bound.height.is_flexible() { let final_height = size_bound.validated_height( final_height, - placement_resolver.get_available().height, - placement_resolver.get_available().height, + placement_resolver.get_available().height(), + placement_resolver.get_available().height(), &parent_limits, - interface_settings.scaling.get(), + application.get_scaling(), ); - size.height = Some(final_height); + + size = App::PartialSize::new(size.width(), Some(final_height)); placement_resolver.register_height(final_height); } @@ -90,7 +87,7 @@ impl ContainerState { final_height } - fn get_next_element(&self, start_index: usize, focus_mode: FocusMode, wrapped_around: &mut bool) -> Option<ElementCell> { + pub fn get_next_element(&self, start_index: usize, focus_mode: FocusMode, wrapped_around: &mut bool) -> Option<ElementCell<App>> { if self.elements.is_empty() { return None; } @@ -123,16 +120,16 @@ impl ContainerState { } } - fn is_focusable<const SELF_FOCUS: bool>(&self) -> bool { + pub fn is_focusable<const SELF_FOCUS: bool>(&self) -> bool { SELF_FOCUS || self.elements.iter().any(|element| element.borrow().is_focusable()) } - fn focus_next<const SELF_FOCUS: bool>( + pub fn focus_next<const SELF_FOCUS: bool>( &self, - self_cell: ElementCell, - caller_cell: Option<ElementCell>, + self_cell: ElementCell<App>, + caller_cell: Option<ElementCell<App>>, focus: Focus, - ) -> Option<ElementCell> { + ) -> Option<ElementCell<App>> { if focus.downwards { if SELF_FOCUS { if focus.mode == FocusMode::FocusPrevious { @@ -246,7 +243,7 @@ impl ContainerState { focusable_element } - fn restore_focus(&self, self_cell: ElementCell) -> Option<ElementCell> { + pub fn restore_focus(&self, self_cell: ElementCell<App>) -> Option<ElementCell<App>> { if let Some(index) = self.focus_cache.get() && !self.elements.is_empty() { @@ -273,13 +270,18 @@ impl ContainerState { }) } - pub fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode, hoverable: bool) -> HoverInformation { - let absolute_position = ScreenPosition::from_size(mouse_position - self.state.cached_position); - - if absolute_position.left >= 0.0 - && absolute_position.top >= 0.0 - && absolute_position.left <= self.state.cached_size.width - && absolute_position.top <= self.state.cached_size.height + pub fn hovered_element( + &self, + mouse_position: App::Position, + mouse_mode: &App::MouseInputMode, + hoverable: bool, + ) -> HoverInformation<App> { + let absolute_position = mouse_position.relative_to(self.state.cached_position); + + if absolute_position.left() >= 0.0 + && absolute_position.top() >= 0.0 + && absolute_position.left() <= self.state.cached_size.width() + && absolute_position.top() <= self.state.cached_size.height() { for element in &self.elements { match element.borrow().hovered_element(absolute_position, mouse_mode) { @@ -299,20 +301,18 @@ impl ContainerState { pub fn render( &self, - renderer: &mut ElementRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, + renderer: &mut ElementRenderer<App>, + application: &App, + theme: &App::Theme, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + mouse_mode: &App::MouseInputMode, second_theme: bool, ) { self.elements.iter().for_each(|element| { renderer.render_element( &*element.borrow(), - state_provider, - interface_settings, + application, theme, hovered_element, focused_element, diff --git a/korangar_interface/src/elements/containers/scroll.rs b/korangar_interface/src/elements/containers/scroll.rs new file mode 100644 index 00000000..8c867464 --- /dev/null +++ b/korangar_interface/src/elements/containers/scroll.rs @@ -0,0 +1,170 @@ +use std::cell::RefCell; +use std::rc::Weak; + +use super::ContainerState; +use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait, PositionTrait, PositionTraitExt, SizeTrait, SizeTraitExt}; +use crate::elements::{Element, ElementCell, ElementState, Focus}; +use crate::event::{ChangeEvent, HoverInformation}; +use crate::layout::{PlacementResolver, SizeBound}; +use crate::theme::{ButtonTheme, InterfaceTheme}; +use crate::ColorSelector; + +const SCROLL_SPEED: f32 = 0.8; + +pub struct ScrollView<App> +where + App: Application, +{ + scroll: f32, + children_height: f32, + state: ContainerState<App>, + size_bound: SizeBound, + background_color: Option<ColorSelector<App>>, +} + +impl<App> ScrollView<App> +where + App: Application, +{ + pub fn new(elements: Vec<ElementCell<App>>, size_bound: SizeBound) -> Self { + let scroll = 0.0; + let children_height = 0.0; + let state = ContainerState::new(elements); + let background_color = None; + + Self { + scroll, + children_height, + state, + size_bound, + background_color, + } + } + + pub fn with_background_color(mut self, background_color: impl Fn(&App::Theme) -> App::Color + 'static) -> Self { + self.background_color = Some(Box::new(background_color)); + self + } + + fn clamp_scroll(&mut self) { + self.scroll = self + .scroll + .clamp(0.0, (self.children_height - self.state.state.cached_size.height()).max(0.0)); + } + + pub fn link_back( + &mut self, + weak_self: Weak<std::cell::RefCell<dyn crate::elements::Element<App>>>, + weak_parent: Option<Weak<std::cell::RefCell<dyn crate::elements::Element<App>>>>, + ) { + self.state.link_back(weak_self, weak_parent) + } +} + +impl<App> Element<App> for ScrollView<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state.state + } + + fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element<App>>>, weak_parent: Option<Weak<RefCell<dyn Element<App>>>>) { + self.state.link_back(weak_self, weak_parent); + } + + fn is_focusable(&self) -> bool { + self.state.is_focusable::<false>() + } + + fn focus_next(&self, self_cell: ElementCell<App>, caller_cell: Option<ElementCell<App>>, focus: Focus) -> Option<ElementCell<App>> { + self.state.focus_next::<false>(self_cell, caller_cell, focus) + } + + fn restore_focus(&self, self_cell: ElementCell<App>) -> Option<ElementCell<App>> { + self.state.restore_focus(self_cell) + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, application: &App, theme: &App::Theme) { + self.children_height = self + .state + .resolve(placement_resolver, application, theme, &self.size_bound, App::Size::zero()); + self.clamp_scroll(); + } + + fn update(&mut self) -> Option<ChangeEvent> { + self.state.update() + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + let absolute_position = mouse_position.relative_to(self.state.state.cached_position); + + if absolute_position.left() >= 0.0 + && absolute_position.top() >= 0.0 + && absolute_position.left() <= self.state.state.cached_size.width() + && absolute_position.top() <= self.state.state.cached_size.height() + { + for element in &self.state.elements { + match element + .borrow() + .hovered_element(absolute_position.combined(App::Position::only_top(self.scroll)), mouse_mode) + { + HoverInformation::Hovered => return HoverInformation::Element(element.clone()), + HoverInformation::Missed => {} + hover_information => return hover_information, + } + } + + if mouse_mode.is_none() { + return HoverInformation::Hovered; + } + } + + HoverInformation::Missed + } + + fn scroll(&mut self, delta: f32) -> Option<ChangeEvent> { + self.scroll -= delta * SCROLL_SPEED; + self.clamp_scroll(); + Some(ChangeEvent::RENDER_WINDOW) + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + mouse_mode: &App::MouseInputMode, + second_theme: bool, + ) { + let mut renderer = self + .state + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + if let Some(color_selector) = &self.background_color { + renderer.render_background(theme.button().corner_radius(), color_selector(theme)); + } + + renderer.set_scroll(self.scroll); + + self.state.render( + &mut renderer, + application, + theme, + hovered_element, + focused_element, + mouse_mode, + second_theme, + ); + } +} diff --git a/korangar_interface/src/elements/miscellanious/headline.rs b/korangar_interface/src/elements/miscellanious/headline.rs new file mode 100644 index 00000000..f7fcefc7 --- /dev/null +++ b/korangar_interface/src/elements/miscellanious/headline.rs @@ -0,0 +1,72 @@ +use crate::application::{Application, InterfaceRenderer}; +use crate::elements::{Element, ElementState}; +use crate::layout::{PlacementResolver, SizeBound}; +use crate::theme::{InterfaceTheme, LabelTheme}; + +pub struct Headline<App> +where + App: Application, +{ + display: String, + size_bound: SizeBound, + state: ElementState<App>, +} + +impl<App> Headline<App> +where + App: Application, +{ + pub fn new(display: String, size_bound: SizeBound) -> Self { + Self { + display, + size_bound, + state: Default::default(), + } + } +} + +impl<App> Element<App> for Headline<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn is_focusable(&self) -> bool { + false + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, _theme: &App::Theme) { + self.state.resolve(placement_resolver, &self.size_bound); + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + _hovered_element: Option<&dyn Element<App>>, + _focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + renderer.render_text( + &self.display, + theme.label().text_offset(), + theme.label().foreground_color(), + theme.label().font_size(), + ); + } +} diff --git a/src/interface/elements/miscellanious/input/builder.rs b/korangar_interface/src/elements/miscellanious/input/builder.rs similarity index 51% rename from src/interface/elements/miscellanious/input/builder.rs rename to korangar_interface/src/elements/miscellanious/input/builder.rs index b69c93e1..1b00a58f 100644 --- a/src/interface/elements/miscellanious/input/builder.rs +++ b/korangar_interface/src/elements/miscellanious/input/builder.rs @@ -1,26 +1,34 @@ use std::fmt::Display; - -use procedural::dimension_bound; +use std::marker::PhantomData; use super::{EnterAction, InputField}; -use crate::interface::builder::{Set, Unset}; -use crate::interface::*; +use crate::application::Application; +use crate::builder::{Set, Unset}; +use crate::event::ClickAction; +use crate::layout::DimensionBound; +use crate::state::PlainTrackedState; /// Type state [`InputField`] builder. This builder utilizes the type system to /// prevent calling the same method multiple times and calling /// [`build`](Self::build) before the mandatory methods have been called. #[must_use = "`build` needs to be called"] -pub struct InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> { - input_state: STATE, - ghost_text: TEXT, - enter_action: ACTION, +pub struct InputFieldBuilder<App, State, Text, Action, Length, Hidden, Width> +where + App: Application, +{ + input_state: State, + ghost_text: Text, + enter_action: Action, length: usize, hidden: bool, width_bound: DimensionBound, - marker: PhantomData<(LENGTH, HIDDEN, WIDTH)>, + marker: PhantomData<(App, Length, Hidden, Width)>, } -impl InputFieldBuilder<Unset, Unset, Unset, Unset, Unset, Unset> { +impl<App> InputFieldBuilder<App, Unset, Unset, Unset, Unset, Unset, Unset> +where + App: Application, +{ pub fn new() -> Self { Self { input_state: Unset, @@ -28,14 +36,20 @@ impl InputFieldBuilder<Unset, Unset, Unset, Unset, Unset, Unset> { enter_action: Unset, length: 0, hidden: false, - width_bound: dimension_bound!(100%), + width_bound: DimensionBound::RELATIVE_ONE_HUNDRED, marker: PhantomData, } } } -impl<TEXT, ACTION, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<Unset, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> { - pub fn with_state(self, state: TrackedState<String>) -> InputFieldBuilder<TrackedState<String>, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> { +impl<App, Text, Action, Length, Hidden, Width> InputFieldBuilder<App, Unset, Text, Action, Length, Hidden, Width> +where + App: Application, +{ + pub fn with_state( + self, + state: PlainTrackedState<String>, + ) -> InputFieldBuilder<App, PlainTrackedState<String>, Text, Action, Length, Hidden, Width> { InputFieldBuilder { input_state: state, ..self @@ -43,22 +57,28 @@ impl<TEXT, ACTION, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<Unset, TEXT, ACTION, } } -impl<STATE, ACTION, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<STATE, Unset, ACTION, LENGTH, HIDDEN, WIDTH> { +impl<App, State, Action, Length, Hidden, Width> InputFieldBuilder<App, State, Unset, Action, Length, Hidden, Width> +where + App: Application, +{ /// Set the text that will be displayed when the [`InputField`] is empty. - pub fn with_ghost_text<TEXT: Display + 'static>( - self, - ghost_text: TEXT, - ) -> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> { + pub fn with_ghost_text<Text>(self, ghost_text: Text) -> InputFieldBuilder<App, State, Text, Action, Length, Hidden, Width> + where + Text: Display + 'static, + { InputFieldBuilder { ghost_text, ..self } } } -impl<STATE, TEXT, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<STATE, TEXT, Unset, LENGTH, HIDDEN, WIDTH> { +impl<App, State, Text, Length, Hidden, Width> InputFieldBuilder<App, State, Text, Unset, Length, Hidden, Width> +where + App: Application, +{ /// Set an action that will be executed when the user presses the enter key. pub fn with_enter_action( self, - enter_action: impl FnMut() -> Vec<ClickAction> + 'static, - ) -> InputFieldBuilder<STATE, TEXT, EnterAction, LENGTH, HIDDEN, WIDTH> { + enter_action: impl FnMut() -> Vec<ClickAction<App>> + 'static, + ) -> InputFieldBuilder<App, State, Text, EnterAction<App>, Length, Hidden, Width> { InputFieldBuilder { enter_action: Box::new(enter_action), ..self @@ -66,9 +86,12 @@ impl<STATE, TEXT, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<STATE, TEXT, Unset, L } } -impl<STATE, TEXT, ACTION, HIDDEN, WIDTH> InputFieldBuilder<STATE, TEXT, ACTION, Unset, HIDDEN, WIDTH> { +impl<App, State, Text, Action, Hidden, Width> InputFieldBuilder<App, State, Text, Action, Unset, Hidden, Width> +where + App: Application, +{ /// Set the maximum number of allowed characters. - pub fn with_length(self, length: usize) -> InputFieldBuilder<STATE, TEXT, ACTION, Set, HIDDEN, WIDTH> { + pub fn with_length(self, length: usize) -> InputFieldBuilder<App, State, Text, Action, Set, Hidden, Width> { InputFieldBuilder { length, marker: PhantomData, @@ -77,9 +100,12 @@ impl<STATE, TEXT, ACTION, HIDDEN, WIDTH> InputFieldBuilder<STATE, TEXT, ACTION, } } -impl<STATE, TEXT, ACTION, LENGTH, WIDTH> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, Unset, WIDTH> { +impl<App, State, Text, Action, Length, Width> InputFieldBuilder<App, State, Text, Action, Length, Unset, Width> +where + App: Application, +{ /// Only show text as `*` characters. Useful for password fields. - pub fn hidden(self) -> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, Set, WIDTH> { + pub fn hidden(self) -> InputFieldBuilder<App, State, Text, Action, Length, Set, Width> { InputFieldBuilder { hidden: true, marker: PhantomData, @@ -88,8 +114,11 @@ impl<STATE, TEXT, ACTION, LENGTH, WIDTH> InputFieldBuilder<STATE, TEXT, ACTION, } } -impl<STATE, TEXT, ACTION, LENGTH, HIDDEN> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, Unset> { - pub fn with_width_bound(self, width_bound: DimensionBound) -> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, Set> { +impl<App, State, Text, Action, Length, Hidden> InputFieldBuilder<App, State, Text, Action, Length, Hidden, Unset> +where + App: Application, +{ + pub fn with_width_bound(self, width_bound: DimensionBound) -> InputFieldBuilder<App, State, Text, Action, Length, Hidden, Set> { InputFieldBuilder { width_bound, marker: PhantomData, @@ -98,9 +127,10 @@ impl<STATE, TEXT, ACTION, LENGTH, HIDDEN> InputFieldBuilder<STATE, TEXT, ACTION, } } -impl<TEXT, HIDDEN, WIDTH> InputFieldBuilder<TrackedState<String>, TEXT, EnterAction, Set, HIDDEN, WIDTH> +impl<App, Text, Hidden, Width> InputFieldBuilder<App, PlainTrackedState<String>, Text, EnterAction<App>, Set, Hidden, Width> where - TEXT: Display + 'static, + App: Application, + Text: Display + 'static, { /// Take the builder and turn it into a [`InputField`]. /// @@ -108,7 +138,7 @@ where /// [`with_ghost_text`](Self::with_ghost_text), /// [`with_enter_action`](Self::with_enter_action), /// and [`with_length`](Self::with_length) have been called on the builder. - pub fn build(self) -> InputField<TEXT> { + pub fn build(self) -> InputField<App, Text> { let Self { input_state, ghost_text, diff --git a/korangar_interface/src/elements/miscellanious/input/mod.rs b/korangar_interface/src/elements/miscellanious/input/mod.rs new file mode 100644 index 00000000..5202a0c4 --- /dev/null +++ b/korangar_interface/src/elements/miscellanious/input/mod.rs @@ -0,0 +1,163 @@ +mod builder; + +use std::fmt::Display; + +pub use self::builder::InputFieldBuilder; +use crate::application::{ + Application, CornerRadiusTraitExt, InterfaceRenderer, MouseInputModeTrait, PositionTrait, PositionTraitExt, ScalingTrait, SizeTrait, +}; +use crate::elements::{Element, ElementState}; +use crate::event::{ChangeEvent, ClickAction, HoverInformation}; +use crate::layout::{DimensionBound, PlacementResolver}; +use crate::state::{PlainTrackedState, TrackedState, ValueState}; +use crate::theme::{InputTheme, InterfaceTheme}; + +/// Local type alias to simplify the builder. +type EnterAction<App> = Box<dyn FnMut() -> Vec<ClickAction<App>>>; + +pub struct InputField<App, Text> +where + App: Application, + Text: Display + 'static, +{ + input_state: PlainTrackedState<String>, + ghost_text: Text, + enter_action: EnterAction<App>, + length: usize, + hidden: bool, + width_bound: DimensionBound, + state: ElementState<App>, +} + +impl<App, Text> InputField<App, Text> +where + App: Application, + Text: Display + 'static, +{ + fn remove_character(&mut self) -> Vec<ClickAction<App>> { + self.input_state.with_mut(|input_state| { + if input_state.is_empty() { + return ValueState::Unchanged(Vec::new()); + } + + input_state.pop(); + + ValueState::Mutated(vec![ClickAction::ChangeEvent(ChangeEvent::RENDER_WINDOW)]) + }) + } + + fn add_character(&mut self, character: char) -> Vec<ClickAction<App>> { + self.input_state.with_mut(|input_state| { + if input_state.len() >= self.length { + return ValueState::Unchanged(Vec::new()); + } + + input_state.push(character); + + ValueState::Mutated(vec![ClickAction::ChangeEvent(ChangeEvent::RENDER_WINDOW)]) + }) + } +} + +impl<App, Text> Element<App> for InputField<App, Text> +where + App: Application, + Text: Display + 'static, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + let size_bound = self.width_bound.add_height(theme.input().height_bound()); + self.state.resolve(placement_resolver, &size_bound); + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + match mouse_mode.is_none() { + true => self.state.hovered_element(mouse_position), + false => HoverInformation::Missed, + } + } + + fn left_click(&mut self, _update: &mut bool) -> Vec<ClickAction<App>> { + vec![ClickAction::FocusElement] + } + + fn input_character(&mut self, character: char) -> Vec<ClickAction<App>> { + match character { + '\u{8}' | '\u{7f}' => self.remove_character(), + '\r' => (self.enter_action)(), + character => self.add_character(character), + } + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let input_state = self.input_state.get(); + let is_hovererd = self.is_element_self(hovered_element); + let is_focused = self.is_element_self(focused_element); + let text_offset = theme.input().text_offset(); + + let text = if input_state.is_empty() && !is_focused { + self.ghost_text.to_string() + } else if self.hidden { + input_state.chars().map(|_| '*').collect() + } else { + input_state.clone() + }; + + let background_color = if is_hovererd { + theme.input().hovered_background_color() + } else if is_focused { + theme.input().focused_background_color() + } else { + theme.input().background_color() + }; + + let text_color = if input_state.is_empty() && !is_focused { + theme.input().ghost_text_color() + } else if is_focused { + theme.input().focused_text_color() + } else { + theme.input().text_color() + }; + + renderer.render_background(theme.input().corner_radius(), background_color); + renderer.render_text(&text, text_offset, text_color, theme.input().font_size()); + + if is_focused { + let cursor_offset = (text_offset.left() + theme.input().cursor_offset()) * application.get_scaling().get_factor() + + renderer.get_text_dimensions(&text, theme.input().font_size(), f32::MAX).width(); + + let cursor_position = App::Position::only_left(cursor_offset); + let cursor_size = App::Size::new(theme.input().cursor_width(), self.state.cached_size.height()); + + renderer.render_rectangle( + cursor_position, + cursor_size, + App::CornerRadius::zero(), + theme.input().text_color(), + ); + } + } +} diff --git a/src/interface/elements/miscellanious/mod.rs b/korangar_interface/src/elements/miscellanious/mod.rs similarity index 69% rename from src/interface/elements/miscellanious/mod.rs rename to korangar_interface/src/elements/miscellanious/mod.rs index 57595fdf..2b141ca0 100644 --- a/src/interface/elements/miscellanious/mod.rs +++ b/korangar_interface/src/elements/miscellanious/mod.rs @@ -1,19 +1,13 @@ -mod chat; mod headline; mod input; -mod item; mod picklist; -mod skill; mod slider; mod static_label; mod text; -pub use self::chat::ChatBuilder; pub use self::headline::Headline; pub use self::input::InputFieldBuilder; -pub use self::item::ItemBox; pub use self::picklist::PickList; -pub use self::skill::SkillBox; pub use self::slider::Slider; pub use self::static_label::StaticLabel; pub use self::text::Text; diff --git a/korangar_interface/src/elements/miscellanious/picklist.rs b/korangar_interface/src/elements/miscellanious/picklist.rs new file mode 100644 index 00000000..4cdec827 --- /dev/null +++ b/korangar_interface/src/elements/miscellanious/picklist.rs @@ -0,0 +1,214 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait, PositionTraitExt, SizeTraitExt}; +use crate::elements::{ButtonBuilder, Element, ElementState, ElementWrap, ScrollView}; +use crate::event::{ClickAction, HoverInformation}; +use crate::layout::{Dimension, DimensionBound, PlacementResolver, SizeBound}; +use crate::state::{TrackedState, TrackedStateClone}; +use crate::theme::{ButtonTheme, InterfaceTheme}; +use crate::ElementEvent; + +pub struct PickList<App, Key, Value, State, Event> +where + App: Application, + Key: Clone + AsRef<str> + 'static, + Value: Clone + PartialEq + 'static, + State: TrackedState<Value> + 'static, + Event: Clone + ElementEvent<App> + 'static, +{ + options: Vec<(Key, Value)>, + selected: Option<State>, + event: Option<Event>, + width_bound: Option<DimensionBound>, + state: ElementState<App>, + latest_position: Rc<RefCell<App::Position>>, + latest_size: Rc<RefCell<App::Size>>, +} + +// HACK: Workaround for Rust incorrect trait bounds when deriving Option<T> +// where T: !Default. +impl<App, Key, Value, State, Event> Default for PickList<App, Key, Value, State, Event> +where + App: Application, + Key: Clone + AsRef<str> + 'static, + Value: Clone + PartialEq + 'static, + State: TrackedState<Value> + 'static, + Event: Clone + ElementEvent<App> + 'static, +{ + fn default() -> Self { + Self { + options: Default::default(), + selected: Default::default(), + event: Default::default(), + width_bound: Default::default(), + state: Default::default(), + latest_position: Rc::new(RefCell::new(App::Position::zero())), + latest_size: Rc::new(RefCell::new(App::Size::zero())), + } + } +} + +impl<App, Key, Value, State, Event> PickList<App, Key, Value, State, Event> +where + App: Application, + Key: Clone + AsRef<str> + 'static, + Value: Clone + PartialEq + 'static, + State: TrackedState<Value> + 'static, + Event: Clone + ElementEvent<App> + 'static, +{ + pub fn with_options(mut self, options: Vec<(Key, Value)>) -> Self { + self.options = options; + self + } + + pub fn with_selected(mut self, selected: State) -> Self { + self.selected = Some(selected); + self + } + + pub fn with_event(mut self, event: Event) -> Self { + self.event = Some(event); + self + } + + pub fn with_width(mut self, width_bound: DimensionBound) -> Self { + self.width_bound = Some(width_bound); + self + } +} + +impl<App, Key, Value, State, Event> Element<App> for PickList<App, Key, Value, State, Event> +where + App: Application, + Key: Clone + AsRef<str> + 'static, + Value: Clone + PartialEq + 'static, + State: TrackedState<Value> + 'static, + Event: Clone + ElementEvent<App> + 'static, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + let size_bound = self + .width_bound + .as_ref() + .unwrap_or(&DimensionBound::RELATIVE_ONE_HUNDRED) + .add_height(theme.button().height_bound()); + + self.state.resolve(placement_resolver, &size_bound); + + *self.latest_size.borrow_mut() = self.state.cached_size; + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + match mouse_mode.is_none() { + true => self.state.hovered_element(mouse_position), + false => HoverInformation::Missed, + } + } + + fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction<App>> { + let position_tracker = { + let latest_position = Rc::downgrade(&self.latest_position); + move || latest_position.upgrade().map(|position| *position.borrow()) + }; + + let size_tracker = { + let latest_size = Rc::downgrade(&self.latest_size); + move || latest_size.upgrade().map(|size| *size.borrow()) + }; + + let options = self + .options + .iter() + .cloned() + .map(|(text, option)| { + // FIX: What is the behavior here when slected is none? + let mut selected = self.selected.clone().unwrap(); + let mut event = self.event.clone(); + + ButtonBuilder::new() + .with_text(text) + .with_event(Box::new(move || { + selected.set(option.clone()); + let mut actions = vec![ClickAction::ClosePopup]; + + if let Some(event) = &mut event { + actions.extend(event.trigger()); + }; + + actions + })) + .build() + .wrap() + }) + .collect(); + + let size_bound = SizeBound { + minimum_height: Some(Dimension::Super), + maximum_height: Some(Dimension::Super), + ..SizeBound::only_height(Dimension::Flexible) + }; + + let element = ScrollView::new(options, size_bound) + .with_background_color(|theme| theme.button().background_color()) + .wrap(); + + vec![ClickAction::OpenPopup { + element, + position_tracker: Box::new(position_tracker), + size_tracker: Box::new(size_tracker), + }] + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let highlighted = self.is_element_self(hovered_element) || self.is_element_self(focused_element); + let background_color = match highlighted { + true => theme.button().hovered_background_color(), + false => theme.button().background_color(), + }; + + renderer.render_background(theme.button().corner_radius(), background_color); + + *self.latest_position.borrow_mut() = renderer.get_position(); + + let foreground_color = match highlighted { + true => theme.button().hovered_foreground_color(), + false => theme.button().foreground_color(), + }; + + // FIX: Don't unwrap. Fix logic + let current_state = self.selected.as_ref().map(|state| state.cloned()).unwrap(); + + if let Some((text, _)) = self.options.iter().find(|(_, value)| *value == current_state) { + renderer.render_text( + text.as_ref(), + theme.button().text_offset(), + foreground_color, + theme.button().font_size(), + ); + } + } +} diff --git a/korangar_interface/src/elements/miscellanious/slider.rs b/korangar_interface/src/elements/miscellanious/slider.rs new file mode 100644 index 00000000..0b42d849 --- /dev/null +++ b/korangar_interface/src/elements/miscellanious/slider.rs @@ -0,0 +1,144 @@ +use std::cmp::PartialOrd; + +use num::traits::NumOps; +use num::{clamp, NumCast, Zero}; + +use crate::application::{ + Application, CornerRadiusTraitExt, InterfaceRenderer, MouseInputModeTrait, PositionTrait, PositionTraitExt, ScalingTrait, SizeTrait, + SizeTraitExt, +}; +use crate::elements::{Element, ElementState}; +use crate::event::{ChangeEvent, ClickAction, HoverInformation}; +use crate::layout::PlacementResolver; +use crate::theme::{ButtonTheme, InterfaceTheme, SliderTheme}; + +pub struct Slider<App, Value> +where + App: Application, + Value: Zero + NumOps + NumCast + Copy + PartialOrd + 'static, +{ + reference: &'static Value, + minimum_value: Value, + maximum_value: Value, + change_event: Option<ChangeEvent>, + cached_value: Value, + state: ElementState<App>, +} + +impl<App, Value> Slider<App, Value> +where + App: Application, + Value: Zero + NumOps + NumCast + Copy + PartialOrd + 'static, +{ + pub fn new(reference: &'static Value, minimum_value: Value, maximum_value: Value, change_event: Option<ChangeEvent>) -> Self { + Self { + reference, + minimum_value, + maximum_value, + change_event, + cached_value: Value::zero(), + state: Default::default(), + } + } +} + +impl<App, Value> Element<App> for Slider<App, Value> +where + App: Application, + Value: Zero + NumOps + NumCast + Copy + PartialOrd + 'static, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + self.state.resolve(placement_resolver, &theme.slider().size_bound()); + } + + fn update(&mut self) -> Option<ChangeEvent> { + let current_value = *self.reference; + + if self.cached_value != current_value { + self.cached_value = current_value; + return Some(ChangeEvent::RENDER_WINDOW); + } + + None + } + + fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + if mouse_mode.is_none() { + self.state.hovered_element(mouse_position) + } else if mouse_mode.is_self_dragged(self) { + HoverInformation::Hovered + } else { + HoverInformation::Missed + } + } + + fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction<App>> { + vec![ClickAction::DragElement] + } + + fn drag(&mut self, mouse_delta: App::Position) -> Option<ChangeEvent> { + let total_range = self.maximum_value.to_f32().unwrap() - self.minimum_value.to_f32().unwrap(); + let raw_value = self.cached_value.to_f32().unwrap() + (mouse_delta.left() * total_range * 0.005); + let new_value = clamp( + raw_value, + self.minimum_value.to_f32().unwrap(), + self.maximum_value.to_f32().unwrap(), + ); + + // SAFETY: Obviously this is totally unsafe, but considering this is a debug + // tool I think it's acceptable. + unsafe { + #[allow(invalid_reference_casting)] + std::ptr::write(self.reference as *const Value as *mut Value, Value::from(new_value).unwrap()); + } + self.change_event + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + hovered_element: Option<&dyn Element<App>>, + _focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + if self.is_element_self(hovered_element) { + renderer.render_background(theme.button().corner_radius(), theme.slider().background_color()); + } + + let bar_size = App::Size::new(self.state.cached_size.width() * 0.9, self.state.cached_size.height() / 4.0); + let offset = App::Position::from_size((self.state.cached_size.shrink(bar_size)).halved()); + + renderer.render_rectangle(offset, bar_size, App::CornerRadius::uniform(0.5), theme.slider().rail_color()); + + let knob_size = App::Size::new( + 20.0 * application.get_scaling().get_factor(), + self.state.cached_size.height() * 0.8, + ); + let total_range = self.maximum_value - self.minimum_value; + let offset = App::Position::new( + (self.state.cached_size.width() - knob_size.width()) / total_range.to_f32().unwrap() + * (self.cached_value.to_f32().unwrap() - self.minimum_value.to_f32().unwrap()), + (self.state.cached_size.height() - knob_size.height()) / 2.0, + ); + + renderer.render_rectangle(offset, knob_size, App::CornerRadius::uniform(4.0), theme.slider().knob_color()); + } +} diff --git a/korangar_interface/src/elements/miscellanious/static_label.rs b/korangar_interface/src/elements/miscellanious/static_label.rs new file mode 100644 index 00000000..49053c93 --- /dev/null +++ b/korangar_interface/src/elements/miscellanious/static_label.rs @@ -0,0 +1,80 @@ +use crate::application::{Application, InterfaceRenderer, PartialSizeTrait, ScalingTrait, SizeTrait}; +use crate::elements::{Element, ElementState}; +use crate::layout::{Dimension, PlacementResolver}; +use crate::theme::{InterfaceTheme, LabelTheme}; + +pub struct StaticLabel<App> +where + App: Application, +{ + label: String, + state: ElementState<App>, +} + +impl<App> StaticLabel<App> +where + App: Application, +{ + pub fn new(label: String) -> Self { + Self { + label, + state: Default::default(), + } + } +} + +impl<App> Element<App> for StaticLabel<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, application: &App, theme: &App::Theme) { + let mut size_bound = theme.label().size_bound(); + + let size = placement_resolver.get_text_dimensions( + &self.label, + theme.label().font_size(), + theme.label().text_offset(), + application.get_scaling(), + placement_resolver.get_available().width() / 2.0, // TODO: make better + ); + + size_bound.height = Dimension::Absolute(f32::max(size.height() / application.get_scaling().get_factor(), 14.0)); // TODO: make better + + self.state.resolve(placement_resolver, &size_bound); + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + _hovered_element: Option<&dyn Element<App>>, + _focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + renderer.render_background(theme.label().corner_radius(), theme.label().background_color()); + + renderer.render_text( + &self.label, + theme.label().text_offset(), + theme.label().foreground_color(), + theme.label().font_size(), + ); + } +} diff --git a/korangar_interface/src/elements/miscellanious/text.rs b/korangar_interface/src/elements/miscellanious/text.rs new file mode 100644 index 00000000..25cc359f --- /dev/null +++ b/korangar_interface/src/elements/miscellanious/text.rs @@ -0,0 +1,133 @@ +use crate::application::{Application, FontSizeTrait, InterfaceRenderer, PositionTraitExt}; +use crate::elements::{Element, ElementState}; +use crate::layout::{Dimension, DimensionBound, PlacementResolver}; +use crate::theme::{ButtonTheme, InterfaceTheme}; +use crate::{ColorSelector, FontSizeSelector}; + +pub struct Text<App, T> +where + App: Application, + // TODO: Would be nice to call this `Text` but it makes `#[derive(Default)]` fail. + T: AsRef<str> + 'static, +{ + text: Option<T>, + foreground_color: Option<ColorSelector<App>>, + width_bound: Option<DimensionBound>, + font_size: Option<FontSizeSelector<App>>, + state: ElementState<App>, +} + +impl<App, T> Default for Text<App, T> +where + App: Application, + T: AsRef<str> + 'static, +{ + fn default() -> Self { + Self { + text: Default::default(), + foreground_color: Default::default(), + width_bound: Default::default(), + font_size: Default::default(), + state: Default::default(), + } + } +} + +impl<App, T> Text<App, T> +where + App: Application, + T: AsRef<str> + 'static, +{ + pub fn with_text(mut self, text: T) -> Self { + self.text = Some(text); + self + } + + pub fn with_foreground_color(mut self, foreground_color: impl Fn(&App::Theme) -> App::Color + 'static) -> Self { + self.foreground_color = Some(Box::new(foreground_color)); + self + } + + pub fn with_font_size(mut self, font_size: impl Fn(&App::Theme) -> App::FontSize + 'static) -> Self { + self.font_size = Some(Box::new(font_size)); + self + } + + pub fn with_width(mut self, width_bound: DimensionBound) -> Self { + self.width_bound = Some(width_bound); + self + } + + fn get_font_size(&self, theme: &App::Theme) -> App::FontSize { + self.font_size + .as_ref() + .map(|closure| closure(theme)) + .unwrap_or(theme.button().font_size()) + } +} + +impl<App, T> Element<App> for Text<App, T> +where + App: Application, + T: AsRef<str> + 'static, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + let height_bound = DimensionBound { + size: Dimension::Absolute(self.get_font_size(theme).get_value()), + minimum_size: None, + maximum_size: None, + }; + + let size_bound = self + .width_bound + .as_ref() + .unwrap_or(&DimensionBound::RELATIVE_ONE_HUNDRED) + .add_height(height_bound); + + self.state.resolve(placement_resolver, &size_bound); + } + + fn is_focusable(&self) -> bool { + false + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + _hovered_element: Option<&dyn Element<App>>, + _focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + let foreground_color = self + .foreground_color + .as_ref() + .map(|closure| closure(theme)) + .unwrap_or(theme.button().foreground_color()); + + let text = self.text.as_ref().unwrap(); + renderer.render_text( + text.as_ref(), + App::Position::zero(), + foreground_color, + self.get_font_size(theme), + ); + } +} diff --git a/korangar_interface/src/elements/mod.rs b/korangar_interface/src/elements/mod.rs new file mode 100644 index 00000000..6fe07a36 --- /dev/null +++ b/korangar_interface/src/elements/mod.rs @@ -0,0 +1,14 @@ +#[macro_use] +mod base; +mod buttons; +mod containers; +mod miscellanious; +mod prototype; +mod value; + +pub use self::base::*; +pub use self::buttons::*; +pub use self::containers::*; +pub use self::miscellanious::*; +pub use self::prototype::*; +pub use self::value::*; diff --git a/src/interface/elements/prototype.rs b/korangar_interface/src/elements/prototype.rs similarity index 59% rename from src/interface/elements/prototype.rs rename to korangar_interface/src/elements/prototype.rs index 097f34b0..15aaca60 100644 --- a/src/interface/elements/prototype.rs +++ b/korangar_interface/src/elements/prototype.rs @@ -2,13 +2,15 @@ use std::fmt::Display; use std::net::Ipv4Addr; use std::rc::Rc; -use cgmath::{Quaternion, Rad, Vector2, Vector3, Vector4}; +use super::{Container, ElementCell, ElementWrap, Expandable, StaticLabel, StringValue}; +use crate::application::Application; +use crate::layout::{DimensionBound, SizeBound}; -use crate::graphics::Color; -use crate::interface::{ElementCell, *}; - -pub trait PrototypeElement { - fn to_element(&self, display: String) -> ElementCell; +pub trait PrototypeElement<App> +where + App: Application, +{ + fn to_element(&self, display: String) -> ElementCell<App>; } pub trait ElementDisplay { @@ -18,11 +20,16 @@ pub trait ElementDisplay { // workaround for not having negative trait bounds or better specialization auto trait NoDisplay {} impl !NoDisplay for f32 {} -impl<T> !NoDisplay for Vector2<T> {} -impl<T> !NoDisplay for Vector3<T> {} -impl<T> !NoDisplay for Vector4<T> {} -impl<T> !NoDisplay for Quaternion<T> {} -impl<T> !NoDisplay for Rad<T> {} +#[cfg(feature = "cgmath")] +impl<T> !NoDisplay for cgmath::Vector2<T> {} +#[cfg(feature = "cgmath")] +impl<T> !NoDisplay for cgmath::Vector3<T> {} +#[cfg(feature = "cgmath")] +impl<T> !NoDisplay for cgmath::Vector4<T> {} +#[cfg(feature = "cgmath")] +impl<T> !NoDisplay for cgmath::Quaternion<T> {} +#[cfg(feature = "cgmath")] +impl<T> !NoDisplay for cgmath::Rad<T> {} impl !NoDisplay for Ipv4Addr {} impl<T> ElementDisplay for T @@ -40,19 +47,22 @@ impl ElementDisplay for f32 { } } -impl<T: ElementDisplay> ElementDisplay for Vector2<T> { +#[cfg(feature = "cgmath")] +impl<T: ElementDisplay> ElementDisplay for cgmath::Vector2<T> { fn display(&self) -> String { format!("{}, {}", self.x.display(), self.y.display()) } } -impl<T: ElementDisplay> ElementDisplay for Vector3<T> { +#[cfg(feature = "cgmath")] +impl<T: ElementDisplay> ElementDisplay for cgmath::Vector3<T> { fn display(&self) -> String { format!("{}, {}, {}", self.x.display(), self.y.display(), self.z.display()) } } -impl<T: ElementDisplay> ElementDisplay for Vector4<T> { +#[cfg(feature = "cgmath")] +impl<T: ElementDisplay> ElementDisplay for cgmath::Vector4<T> { fn display(&self) -> String { format!( "{}, {}, {}, {}", @@ -64,7 +74,8 @@ impl<T: ElementDisplay> ElementDisplay for Vector4<T> { } } -impl<T: ElementDisplay> ElementDisplay for Quaternion<T> { +#[cfg(feature = "cgmath")] +impl<T: ElementDisplay> ElementDisplay for cgmath::Quaternion<T> { fn display(&self) -> String { format!( "{:.1}, {:.1}, {:.1} - {:.1}", @@ -76,12 +87,14 @@ impl<T: ElementDisplay> ElementDisplay for Quaternion<T> { } } -impl<T: ElementDisplay> ElementDisplay for Rad<T> { +#[cfg(feature = "cgmath")] +impl<T: ElementDisplay> ElementDisplay for cgmath::Rad<T> { fn display(&self) -> String { self.0.display() } } +#[cfg(feature = "cgmath")] impl ElementDisplay for Ipv4Addr { fn display(&self) -> String { self.to_string() @@ -124,41 +137,56 @@ impl NoPrototype for &str {} impl NoPrototype for String {} impl NoPrototype for Ipv4Addr {} -impl<T> PrototypeElement for T +impl<App, T> PrototypeElement<App> for T where + App: Application, T: ElementDisplay + NoPrototype, { - fn to_element(&self, display: String) -> ElementCell { + fn to_element(&self, display: String) -> ElementCell<App> { let elements = vec![StaticLabel::new(display).wrap(), StringValue::new(self.display()).wrap()]; Container::new(elements).wrap() } } -impl PrototypeElement for DimensionBound { - fn to_element(&self, display: String) -> ElementCell { +impl<App> PrototypeElement<App> for DimensionBound +where + App: Application, +{ + fn to_element(&self, display: String) -> ElementCell<App> { let elements = vec![StaticLabel::new(display).wrap()]; Container::new(elements).wrap() } } -impl PrototypeElement for SizeBound { - fn to_element(&self, display: String) -> ElementCell { +impl<App> PrototypeElement<App> for SizeBound +where + App: Application, +{ + fn to_element(&self, display: String) -> ElementCell<App> { let elements = vec![StaticLabel::new(display).wrap()]; Container::new(elements).wrap() } } -impl<T: PrototypeElement> PrototypeElement for std::sync::Arc<T> { - fn to_element(&self, display: String) -> ElementCell { +impl<App, T> PrototypeElement<App> for std::sync::Arc<T> +where + App: Application, + T: PrototypeElement<App>, +{ + fn to_element(&self, display: String) -> ElementCell<App> { self.as_ref().to_element(display) } } -impl<T: PrototypeElement> PrototypeElement for Option<T> { - fn to_element(&self, display: String) -> ElementCell { +impl<App, T> PrototypeElement<App> for Option<T> +where + App: Application, + T: PrototypeElement<App>, +{ + fn to_element(&self, display: String) -> ElementCell<App> { if let Some(value) = self { return value.to_element(display); } @@ -169,8 +197,12 @@ impl<T: PrototypeElement> PrototypeElement for Option<T> { } } -impl<T: PrototypeElement> PrototypeElement for &[T] { - fn to_element(&self, display: String) -> ElementCell { +impl<App, T> PrototypeElement<App> for &[T] +where + App: Application, + T: PrototypeElement<App>, +{ + fn to_element(&self, display: String) -> ElementCell<App> { let elements = self .iter() .enumerate() @@ -181,8 +213,12 @@ impl<T: PrototypeElement> PrototypeElement for &[T] { } } -impl<T: PrototypeElement, const SIZE: usize> PrototypeElement for [T; SIZE] { - fn to_element(&self, display: String) -> ElementCell { +impl<App, T, const SIZE: usize> PrototypeElement<App> for [T; SIZE] +where + App: Application, + T: PrototypeElement<App>, +{ + fn to_element(&self, display: String) -> ElementCell<App> { let elements = self .iter() .enumerate() @@ -193,8 +229,12 @@ impl<T: PrototypeElement, const SIZE: usize> PrototypeElement for [T; SIZE] { } } -impl<T: PrototypeElement> PrototypeElement for Vec<T> { - fn to_element(&self, display: String) -> ElementCell { +impl<App, T> PrototypeElement<App> for Vec<T> +where + App: Application, + T: PrototypeElement<App>, +{ + fn to_element(&self, display: String) -> ElementCell<App> { let elements = self .iter() .enumerate() @@ -205,16 +245,12 @@ impl<T: PrototypeElement> PrototypeElement for Vec<T> { } } -impl PrototypeElement for Color { - fn to_element(&self, display: String) -> ElementCell { - let elements = vec![StaticLabel::new(display).wrap(), ColorValue::new(*self).wrap()]; - - Container::new(elements).wrap() - } -} - -impl<T: PrototypeElement> PrototypeElement for Rc<T> { - fn to_element(&self, display: String) -> ElementCell { +impl<App, T> PrototypeElement<App> for Rc<T> +where + App: Application, + T: PrototypeElement<App>, +{ + fn to_element(&self, display: String) -> ElementCell<App> { (**self).to_element(display) } } diff --git a/korangar_interface/src/elements/value.rs b/korangar_interface/src/elements/value.rs new file mode 100644 index 00000000..59e6d06a --- /dev/null +++ b/korangar_interface/src/elements/value.rs @@ -0,0 +1,68 @@ +use crate::application::{Application, InterfaceRenderer}; +use crate::elements::{Element, ElementState}; +use crate::layout::PlacementResolver; +use crate::theme::{InterfaceTheme, ValueTheme}; + +pub struct StringValue<App> +where + App: Application, +{ + value: String, + state: ElementState<App>, +} + +impl<App> StringValue<App> +where + App: Application, +{ + pub fn new(value: impl Into<String>) -> Self { + Self { + value: value.into(), + state: Default::default(), + } + } +} + +impl<App> Element<App> for StringValue<App> +where + App: Application, +{ + fn get_state(&self) -> &ElementState<App> { + &self.state + } + + fn get_state_mut(&mut self) -> &mut ElementState<App> { + &mut self.state + } + + fn resolve(&mut self, placement_resolver: &mut PlacementResolver<App>, _application: &App, theme: &App::Theme) { + self.state.resolve(placement_resolver, &theme.value().size_bound()); + } + + fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + parent_position: App::Position, + screen_clip: App::Clip, + _hovered_element: Option<&dyn Element<App>>, + _focused_element: Option<&dyn Element<App>>, + _mouse_mode: &App::MouseInputMode, + _second_theme: bool, + ) { + let mut renderer = self + .state + .element_renderer(render_target, renderer, application, parent_position, screen_clip); + + renderer.render_background(theme.value().corner_radius(), theme.value().hovered_background_color()); + + renderer.render_text( + &self.value, + theme.value().text_offset(), + theme.value().foreground_color(), + theme.value().font_size(), + ); + } +} diff --git a/korangar_interface/src/event/action.rs b/korangar_interface/src/event/action.rs new file mode 100644 index 00000000..bd747016 --- /dev/null +++ b/korangar_interface/src/event/action.rs @@ -0,0 +1,26 @@ +use super::ChangeEvent; +use crate::application::Application; +use crate::elements::{ElementCell, FocusMode}; +use crate::windows::PrototypeWindow; +use crate::Tracker; + +pub enum ClickAction<App> +where + App: Application, +{ + FocusElement, + FocusNext(FocusMode), + ChangeEvent(ChangeEvent), + DragElement, + Move(App::DropResource), + MoveInterface, + OpenWindow(Box<dyn PrototypeWindow<App>>), + CloseWindow, + OpenPopup { + element: ElementCell<App>, + position_tracker: Tracker<App::Position>, + size_tracker: Tracker<App::Size>, + }, + ClosePopup, + Custom(App::CustomEvent), +} diff --git a/src/interface/event/change.rs b/korangar_interface/src/event/change.rs similarity index 100% rename from src/interface/event/change.rs rename to korangar_interface/src/event/change.rs diff --git a/korangar_interface/src/event/hover.rs b/korangar_interface/src/event/hover.rs new file mode 100644 index 00000000..e1d7ea95 --- /dev/null +++ b/korangar_interface/src/event/hover.rs @@ -0,0 +1,11 @@ +use crate::application::Application; +use crate::elements::ElementCell; + +pub enum HoverInformation<App> +where + App: Application, +{ + Element(ElementCell<App>), + Hovered, + Missed, +} diff --git a/src/interface/event/mod.rs b/korangar_interface/src/event/mod.rs similarity index 54% rename from src/interface/event/mod.rs rename to korangar_interface/src/event/mod.rs index f981f3a2..57feae0c 100644 --- a/src/interface/event/mod.rs +++ b/korangar_interface/src/event/mod.rs @@ -1,11 +1,7 @@ mod action; mod change; mod hover; -mod item; -mod skill; pub use self::action::ClickAction; pub use self::change::*; pub use self::hover::HoverInformation; -pub use self::item::{ItemMove, ItemSource}; -pub use self::skill::{SkillMove, SkillSource}; diff --git a/src/interface/layout/bound.rs b/korangar_interface/src/layout/bound.rs similarity index 52% rename from src/interface/layout/bound.rs rename to korangar_interface/src/layout/bound.rs index 7418f3ab..b51bd6c0 100644 --- a/src/interface/layout/bound.rs +++ b/korangar_interface/src/layout/bound.rs @@ -1,10 +1,11 @@ -use derive_new::new; -use procedural::size_bound; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::interface::{Dimension, PartialScreenSize, ScreenPosition, ScreenSize}; +use super::Dimension; +use crate::application::{PartialSizeTrait, PositionTrait, ScalingTrait, SizeTrait, SizeTraitExt}; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, new)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct DimensionBound { pub size: Dimension, pub minimum_size: Option<Dimension>, @@ -12,6 +13,12 @@ pub struct DimensionBound { } impl DimensionBound { + pub const RELATIVE_ONE_HUNDRED: Self = Self { + size: Dimension::Relative(100.0), + minimum_size: None, + maximum_size: None, + }; + pub fn add_height(&self, height_bound: DimensionBound) -> SizeBound { SizeBound { width: self.size, @@ -24,7 +31,8 @@ impl DimensionBound { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, new)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SizeBound { pub width: Dimension, pub minimum_width: Option<Dimension>, @@ -35,51 +43,72 @@ pub struct SizeBound { } impl SizeBound { - pub const DEFAULT_FULLY_BOUNDED: Self = size_bound!(200 > 300 < 400, 0 > ? < 80%); - pub const DEFAULT_UNBOUNDED: Self = size_bound!(200 > 300 < 400, ?); + pub(crate) const fn only_height(height: Dimension) -> Self { + Self { + width: Dimension::Relative(100.0), + minimum_width: None, + maximum_width: None, + height, + minimum_height: None, + maximum_height: None, + } + } - pub fn resolve_window(&self, available: ScreenSize, remaining: ScreenSize, scaling: f32) -> PartialScreenSize { + pub(crate) fn resolve_window<Size>(&self, available: impl SizeTrait, remaining: impl SizeTrait, scaling: impl ScalingTrait) -> Size + where + Size: PartialSizeTrait, + { let parent_limits = ParentLimits::default(); - let width = self.width.resolve_width(available.width, remaining.width, None, scaling); - let width = self.validated_width(width, available.width, remaining.width, &parent_limits, scaling); + let width = self.width.resolve_width(available.width(), remaining.width(), None, scaling); + let width = self.validated_width(width, available.width(), remaining.width(), &parent_limits, scaling); let mut height = self .height - .resolve_height(Some(available.height), Some(remaining.height), None, scaling); + .resolve_height(Some(available.height()), Some(remaining.height()), None, scaling); if let Some(height) = &mut height { *height = self.validated_height( *height, - available.height.into(), - remaining.height.into(), + Some(available.height()), + Some(remaining.height()), &parent_limits, scaling, ); } - PartialScreenSize::new(width, height) + Size::new(width, height) } - pub fn resolve_element( + pub(crate) fn resolve_element<Size>( &self, - available: PartialScreenSize, - remaining: PartialScreenSize, + available: impl PartialSizeTrait, + remaining: impl PartialSizeTrait, parent_limits: &ParentLimits, - scaling: f32, - ) -> PartialScreenSize { - let width = self.width.resolve_width(available.width, remaining.width, None, scaling); - let width = self.validated_width(width, available.width, remaining.width, parent_limits, scaling); - - let mut height = self.height.resolve_height(available.height, remaining.height, None, scaling); + scaling: impl ScalingTrait, + ) -> Size + where + Size: PartialSizeTrait, + { + let width = self.width.resolve_width(available.width(), remaining.width(), None, scaling); + let width = self.validated_width(width, available.width(), remaining.width(), parent_limits, scaling); + + let mut height = self.height.resolve_height(available.height(), remaining.height(), None, scaling); if let Some(height) = &mut height { - *height = self.validated_height(*height, available.height, remaining.height, parent_limits, scaling); + *height = self.validated_height(*height, available.height(), remaining.height(), parent_limits, scaling); } - PartialScreenSize::new(width, height) + Size::new(width, height) } - fn validated_width(&self, mut width: f32, available: f32, remaining: f32, parent_limits: &ParentLimits, scaling: f32) -> f32 { + fn validated_width( + &self, + mut width: f32, + available: f32, + remaining: f32, + parent_limits: &ParentLimits, + scaling: impl ScalingTrait, + ) -> f32 { if let Some(maximum_width) = self.maximum_width { let maximum_value = maximum_width.resolve_width(available, remaining, parent_limits.maximum_width, scaling); width = f32::min(width, maximum_value); @@ -93,13 +122,13 @@ impl SizeBound { width } - pub fn validated_height( + pub(crate) fn validated_height( &self, mut height: f32, available: Option<f32>, remaining: Option<f32>, parent_limits: &ParentLimits, - scaling: f32, + scaling: impl ScalingTrait, ) -> f32 { if let Some(maximum_height) = self.maximum_height { let maximum_value = maximum_height.resolve_height(available, remaining, parent_limits.maximum_height, scaling); @@ -114,32 +143,43 @@ impl SizeBound { height } - pub fn validated_window_size(&self, size: ScreenSize, available: ScreenSize, scaling: f32) -> ScreenSize { + pub(crate) fn validated_window_size<Size>(&self, size: impl SizeTrait, available: impl SizeTrait, scaling: impl ScalingTrait) -> Size + where + Size: SizeTrait, + { let parent_limits = ParentLimits::default(); - let width = self.validated_width(size.width, available.width, available.width, &parent_limits, scaling); + let width = self.validated_width(size.width(), available.width(), available.width(), &parent_limits, scaling); let height = self.validated_height( - size.height, - Some(available.height), - Some(available.height), + size.height(), + Some(available.height()), + Some(available.height()), &parent_limits, scaling, ); - ScreenSize { width, height } + Size::new(width, height) } - pub fn validated_position(&self, position: ScreenPosition, size: ScreenSize, available: ScreenSize) -> ScreenPosition { - let half_size = size / 2.0; - let left = f32::clamp(position.left, -half_size.width, available.width - half_size.width); - let top = f32::clamp(position.top, 0.0, available.height - 30.0); - - ScreenPosition { left, top } + pub(crate) fn validated_position<Position>( + &self, + position: impl PositionTrait, + size: impl SizeTrait, + available: impl SizeTrait, + ) -> Position + where + Position: PositionTrait, + { + let half_size = size.halved(); + let left = f32::clamp(position.left(), -half_size.width(), available.width() - half_size.width()); + let top = f32::clamp(position.top(), 0.0, available.height() - 30.0); + + Position::new(left, top) } } #[derive(Debug, Clone, Copy, Default)] -pub struct ParentLimits { +pub(crate) struct ParentLimits { pub minimum_width: Option<f32>, pub maximum_width: Option<f32>, pub minimum_height: Option<f32>, @@ -147,54 +187,60 @@ pub struct ParentLimits { } impl ParentLimits { - pub fn from_bound(size_bound: &SizeBound, available_space: ScreenSize, scaling: f32) -> Self { + pub fn from_bound(size_bound: &SizeBound, available_space: impl SizeTrait, scaling: impl ScalingTrait) -> Self { Self { minimum_width: size_bound .minimum_width - .and_then(|dimension| dimension.try_resolve_width(available_space.width, available_space.width, None, scaling)), + .and_then(|dimension| dimension.try_resolve_width(available_space.width(), available_space.width(), None, scaling)), maximum_width: size_bound .maximum_width - .and_then(|dimension| dimension.try_resolve_width(available_space.width, available_space.width, None, scaling)), + .and_then(|dimension| dimension.try_resolve_width(available_space.width(), available_space.width(), None, scaling)), minimum_height: size_bound.minimum_height.and_then(|dimension| { - dimension.try_resolve_height(Some(available_space.height), Some(available_space.height), None, scaling) + dimension.try_resolve_height(Some(available_space.height()), Some(available_space.height()), None, scaling) }), maximum_height: size_bound.maximum_height.and_then(|dimension| { - dimension.try_resolve_height(Some(available_space.height), Some(available_space.height), None, scaling) + dimension.try_resolve_height(Some(available_space.height()), Some(available_space.height()), None, scaling) }), } } - pub fn derive(&self, size_bound: &SizeBound, available_space: PartialScreenSize, unusable_space: ScreenSize, scaling: f32) -> Self { + pub fn derive( + &self, + size_bound: &SizeBound, + available_space: impl PartialSizeTrait, + unusable_space: impl SizeTrait, + scaling: impl ScalingTrait, + ) -> Self { Self { minimum_width: size_bound.minimum_width.and_then(|dimension| { dimension.try_resolve_width( - available_space.width, - available_space.width, - self.minimum_width.map(|width| (width - unusable_space.width).max(0.0)), + available_space.width(), + available_space.width(), + self.minimum_width.map(|width| (width - unusable_space.width()).max(0.0)), scaling, ) }), maximum_width: size_bound.maximum_width.and_then(|dimension| { dimension.try_resolve_width( - available_space.width, - available_space.width, - self.maximum_width.map(|width| (width - unusable_space.width).max(0.0)), + available_space.width(), + available_space.width(), + self.maximum_width.map(|width| (width - unusable_space.width()).max(0.0)), scaling, ) }), minimum_height: size_bound.minimum_height.and_then(|dimension| { dimension.try_resolve_height( - available_space.height, - available_space.height, - self.minimum_height.map(|height| (height - unusable_space.height).max(0.0)), + available_space.height(), + available_space.height(), + self.minimum_height.map(|height| (height - unusable_space.height()).max(0.0)), scaling, ) }), maximum_height: size_bound.maximum_height.and_then(|dimension| { dimension.try_resolve_height( - available_space.height, - available_space.height, - self.maximum_height.map(|height| (height - unusable_space.height).max(0.0)), + available_space.height(), + available_space.height(), + self.maximum_height.map(|height| (height - unusable_space.height()).max(0.0)), scaling, ) }), diff --git a/src/interface/layout/dimension.rs b/korangar_interface/src/layout/dimension.rs similarity index 67% rename from src/interface/layout/dimension.rs rename to korangar_interface/src/layout/dimension.rs index b5a44af5..ab45793a 100644 --- a/src/interface/layout/dimension.rs +++ b/korangar_interface/src/layout/dimension.rs @@ -1,6 +1,10 @@ +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +use crate::application::ScalingTrait; + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Dimension { Relative(f32), Absolute(f32), @@ -10,21 +14,27 @@ pub enum Dimension { } impl Dimension { - fn resolve_width_inner(&self, available: f32, remaining: f32, parent: Option<f32>, scaling: f32) -> Result<f32, &'static str> { + fn resolve_width_inner( + &self, + available: f32, + remaining: f32, + parent: Option<f32>, + scaling: impl ScalingTrait, + ) -> Result<f32, &'static str> { match *self { Dimension::Relative(precentage) => Ok(available / 100.0 * precentage), - Dimension::Absolute(value) => Ok(value * scaling), + Dimension::Absolute(value) => Ok(value * scaling.get_factor()), Dimension::Remaining => Ok(remaining), Dimension::Super => parent.ok_or("trying to get parent size without a parent"), Dimension::Flexible => Err("the width may not be flexible"), } } - pub fn resolve_width(&self, available: f32, remaining: f32, parent: Option<f32>, scaling: f32) -> f32 { + pub fn resolve_width(&self, available: f32, remaining: f32, parent: Option<f32>, scaling: impl ScalingTrait) -> f32 { self.resolve_width_inner(available, remaining, parent, scaling).unwrap() } - pub fn try_resolve_width(&self, available: f32, remaining: f32, parent: Option<f32>, scaling: f32) -> Option<f32> { + pub fn try_resolve_width(&self, available: f32, remaining: f32, parent: Option<f32>, scaling: impl ScalingTrait) -> Option<f32> { self.resolve_width_inner(available, remaining, parent, scaling).ok() } @@ -33,13 +43,13 @@ impl Dimension { available: Option<f32>, remaining: Option<f32>, parent: Option<f32>, - scaling: f32, + scaling: impl ScalingTrait, ) -> Result<Option<f32>, &'static str> { match *self { Dimension::Relative(precentage) => Ok(Some( available.ok_or("trying to get a relative height from a flexible component")? / 100.0 * precentage, )), - Dimension::Absolute(value) => Ok(Some(value * scaling)), + Dimension::Absolute(value) => Ok(Some(value * scaling.get_factor())), Dimension::Remaining => Ok(Some( remaining.ok_or("trying to get remaining space from a flexible component")?, )), @@ -48,11 +58,23 @@ impl Dimension { } } - pub fn resolve_height(&self, available: Option<f32>, remaining: Option<f32>, parent: Option<f32>, scaling: f32) -> Option<f32> { + pub fn resolve_height( + &self, + available: Option<f32>, + remaining: Option<f32>, + parent: Option<f32>, + scaling: impl ScalingTrait, + ) -> Option<f32> { self.resolve_height_inner(available, remaining, parent, scaling).unwrap() } - pub fn try_resolve_height(&self, available: Option<f32>, remaining: Option<f32>, parent: Option<f32>, scaling: f32) -> Option<f32> { + pub fn try_resolve_height( + &self, + available: Option<f32>, + remaining: Option<f32>, + parent: Option<f32>, + scaling: impl ScalingTrait, + ) -> Option<f32> { self.resolve_height_inner(available, remaining, parent, scaling).ok().flatten() } diff --git a/src/interface/layout/mod.rs b/korangar_interface/src/layout/mod.rs similarity index 59% rename from src/interface/layout/mod.rs rename to korangar_interface/src/layout/mod.rs index c4b054e8..929a3cf9 100644 --- a/src/interface/layout/mod.rs +++ b/korangar_interface/src/layout/mod.rs @@ -1,9 +1,7 @@ mod bound; mod dimension; mod resolver; -mod size; pub use self::bound::{DimensionBound, SizeBound}; pub use self::dimension::Dimension; pub use self::resolver::PlacementResolver; -pub use self::size::{ArrayType, CornerRadius, PartialScreenSize, ScreenClip, ScreenPosition, ScreenSize}; diff --git a/korangar_interface/src/layout/resolver.rs b/korangar_interface/src/layout/resolver.rs new file mode 100644 index 00000000..2eb9db84 --- /dev/null +++ b/korangar_interface/src/layout/resolver.rs @@ -0,0 +1,234 @@ +use super::bound::ParentLimits; +use super::SizeBound; +use crate::application::{ + Application, FontLoaderTrait, FontSizeTraitExt, PartialSizeTrait, PositionTrait, PositionTraitExt, ScalingTrait, SizeTrait, + SizeTraitExt, +}; + +const ELEMENT_THRESHHOLD: f32 = 1.0000; +const REMAINDER_THRESHHOLD: f32 = 0.0001; + +pub struct PlacementResolver<App> +where + App: Application, +{ + font_loader: App::FontLoader, + available_space: App::PartialSize, + parent_limits: ParentLimits, + base_position: App::Position, + horizontal_accumulator: f32, + vertical_offset: f32, + total_height: f32, + border: App::Size, + gaps: App::Size, + scaling: App::Scaling, +} + +impl<App> PlacementResolver<App> +where + App: Application, +{ + pub fn new( + font_loader: App::FontLoader, + screen_size: App::Size, + window_size: App::Size, + size_bound: &SizeBound, + border: App::Size, + gaps: App::Size, + scaling: App::Scaling, + ) -> Self { + let window_size = window_size.shrink(border.scaled(scaling).doubled()); + + // NOTE: I'm not enirely sure why we need to subtract one border here, but if we + // don't the `super` bound is too big. + let parent_limits = ParentLimits::from_bound(size_bound, screen_size.shrink(border.scaled(scaling)), scaling); + let base_position = App::Position::from_size(border.scaled(scaling)); + let horizontal_accumulator = 0.0; + let vertical_offset = 0.0; + let total_height = 0.0; + + let height = (!size_bound.height.is_flexible()).then_some(window_size.height()); + let available_space = App::PartialSize::new(window_size.width(), height); + + Self { + font_loader, + available_space, + parent_limits, + base_position, + horizontal_accumulator, + total_height, + vertical_offset, + border, + gaps, + scaling, + } + } + + pub fn derive(&mut self, size_bound: &SizeBound, offset: App::Position, border: App::Size) -> (Self, App::PartialSize, App::Position) { + let (size, position) = self.allocate(size_bound); + + let border_with_offset = offset.remaining(border.scaled(self.scaling).doubled()); + let available_space = App::PartialSize::new( + size.width() - border_with_offset.width(), + size.height().map(|height| height - border_with_offset.height()), + ); + + let font_loader = self.font_loader.clone(); + let unusable_space = position.remaining(border_with_offset); + let parent_limits = self.parent_limits.derive(size_bound, available_space, unusable_space, self.scaling); + let base_position = offset.offset(border.scaled(self.scaling)); + let horizontal_accumulator = 0.0; + let vertical_offset = 0.0; + let total_height = 0.0; + let gaps = self.gaps; + let scaling = self.scaling; + + let derived_resolver = Self { + font_loader, + available_space, + parent_limits, + base_position, + horizontal_accumulator, + total_height, + vertical_offset, + border, + gaps, + scaling, + }; + + (derived_resolver, size, position) + } + + pub fn get_text_dimensions( + &self, + text: &str, + font_size: App::FontSize, + text_offset: App::Position, + scaling: App::Scaling, + available_width: f32, + ) -> App::Size { + self.font_loader.get_text_dimensions( + text, + font_size.scaled(scaling), + available_width - text_offset.left() * scaling.get_factor(), + ) + } + + pub fn set_gaps(&mut self, gaps: App::Size) { + self.gaps = gaps; + } + + pub fn get_available(&self) -> App::PartialSize { + self.available_space + } + + pub fn get_remaining(&self) -> App::PartialSize { + let remaining_width = self.available_space.width() - self.horizontal_accumulator; + let remaining_height = self + .available_space + .height() + .map(|height| height - self.total_height - self.vertical_offset); + + App::PartialSize::new(remaining_width, remaining_height) + } + + pub fn newline(&mut self) { + self.total_height += self.vertical_offset + self.gaps.height() * self.scaling.get_factor(); + self.base_position = App::Position::new( + self.base_position.left(), + self.base_position.top() + self.vertical_offset + self.gaps.height() * self.scaling.get_factor(), + ); + self.horizontal_accumulator = 0.0; + self.vertical_offset = 0.0; + } + + pub fn register_height(&mut self, height: f32) { + self.vertical_offset = f32::max(self.vertical_offset, height); + } + + pub fn allocate(&mut self, size_bound: &SizeBound) -> (App::PartialSize, App::Position) { + let is_width_absolute = size_bound.width.is_absolute(); + let gaps_add = match is_width_absolute { + true => self.gaps.width() * 2.0, + false => 0.0, + }; + + let mut remaining = self.get_remaining(); + let mut size = size_bound.resolve_element::<App::PartialSize>(self.available_space, remaining, &self.parent_limits, self.scaling); + let mut gaps_subtract = 0.0; + + if remaining.width() < size.width() - REMAINDER_THRESHHOLD { + self.newline(); + remaining = self.get_remaining(); + + if size_bound.width.is_remaining() || size_bound.height.is_remaining() { + size = size_bound.resolve_element(self.available_space, remaining, &self.parent_limits, self.scaling); + } + + size = App::PartialSize::new(f32::min(size.width(), self.available_space.width()), size.height()); + } + + if self.horizontal_accumulator > ELEMENT_THRESHHOLD { + match is_width_absolute { + true => {} + false => gaps_subtract += self.gaps.width() * self.scaling.get_factor(), + } + } + + let position = App::Position::new( + self.base_position.left() + self.horizontal_accumulator + gaps_subtract, + self.base_position.top(), + ); + + self.horizontal_accumulator += size.width() + gaps_add; + + if let Some(height) = size.height() { + self.register_height(height); + } + + if remaining.width() - size.width() > ELEMENT_THRESHHOLD { + match is_width_absolute { + true => {} + false => gaps_subtract += self.gaps.width() * self.scaling.get_factor(), + } + } + + size = App::PartialSize::new(size.width() - gaps_subtract, size.height()); + (size, position) + } + + pub fn allocate_right(&mut self, size_bound: &SizeBound) -> (App::PartialSize, App::Position) { + let mut remaining = self.get_remaining(); + let mut size = size_bound.resolve_element::<App::PartialSize>(self.available_space, remaining, &self.parent_limits, self.scaling); + + if remaining.width() < size.width() - REMAINDER_THRESHHOLD + self.gaps.width() * self.scaling.get_factor() { + self.newline(); + remaining = self.get_remaining(); + + if size_bound.width.is_remaining() || size_bound.height.is_remaining() { + size = size_bound.resolve_element(self.available_space, remaining, &self.parent_limits, self.scaling); + } + } + + let position = App::Position::new( + self.base_position.left() + (self.available_space.width() - size.width() - self.gaps.width() * self.scaling.get_factor()), + self.base_position.top(), + ); + + self.horizontal_accumulator += remaining.width(); + + if let Some(height) = size.height() { + self.register_height(height); + } + + (size, position) + } + + pub(crate) fn get_parent_limits(&self) -> ParentLimits { + self.parent_limits + } + + pub fn final_height(self) -> f32 { + self.total_height + self.vertical_offset + self.border.height() * self.scaling.get_factor() + } +} diff --git a/korangar_interface/src/lib.rs b/korangar_interface/src/lib.rs new file mode 100644 index 00000000..2c438c65 --- /dev/null +++ b/korangar_interface/src/lib.rs @@ -0,0 +1,544 @@ +#![feature(auto_traits)] +#![feature(generic_const_exprs)] +#![feature(let_chains)] +#![feature(negative_impls)] +#![feature(option_zip)] +#![feature(specialization)] +#![feature(type_changing_struct_update)] +#![allow(incomplete_features)] + +pub mod application; +pub mod event; +pub mod layout; +pub mod state; +pub mod theme; +#[macro_use] +pub mod elements; +pub mod builder; +pub mod windows; + +use std::marker::PhantomData; + +use application::{Application, FocusState, InterfaceRenderer, SizeTrait, SizeTraitExt, WindowCache}; +use elements::ElementCell; +use event::{ChangeEvent, ClickAction, HoverInformation}; +use option_ext::OptionExt; +use windows::{PrototypeWindow, Window}; + +// TODO: move this +pub type Selector = Box<dyn Fn() -> bool>; +#[allow(type_alias_bounds)] +pub type ColorSelector<App: Application> = Box<dyn Fn(&App::Theme) -> App::Color>; +#[allow(type_alias_bounds)] +pub type FontSizeSelector<App: Application> = Box<dyn Fn(&App::Theme) -> App::FontSize>; + +// NOTE: To make proc macro work. +mod korangar_interface {} + +pub trait ElementEvent<App> +where + App: Application, +{ + fn trigger(&mut self) -> Vec<ClickAction<App>>; +} + +impl<App, F> ElementEvent<App> for F +where + App: Application, + F: FnMut() -> Vec<ClickAction<App>> + 'static, +{ + fn trigger(&mut self) -> Vec<ClickAction<App>> { + self() + } +} + +#[derive(Clone)] +struct PerWindow; + +#[derive(Clone)] +struct PostUpdate<T> { + resolve: bool, + render: bool, + marker: PhantomData<T>, +} + +impl<T> PostUpdate<T> { + pub fn new() -> Self { + Self { + resolve: false, + render: false, + marker: PhantomData, + } + } + + pub fn render(&mut self) { + self.render = true; + } + + pub fn resolve(&mut self) { + self.resolve = true; + } + + pub fn with_render(mut self) -> Self { + self.render = true; + self + } + + pub fn with_resolve(mut self) -> Self { + self.resolve = true; + self + } + + pub fn needs_render(&self) -> bool { + self.render + } + + pub fn needs_resolve(&self) -> bool { + self.resolve + } + + pub fn take_render(&mut self) -> bool { + let state = self.render; + self.render = false; + state + } + + pub fn take_resolve(&mut self) -> bool { + let state = self.resolve; + self.resolve = false; + state + } +} + +pub type Tracker<T> = Box<dyn Fn() -> Option<T>>; + +pub struct Interface<App> +where + App: Application, +{ + windows: Vec<(Window<App>, PostUpdate<PerWindow>)>, + window_cache: App::Cache, + available_space: App::Size, + post_update: PostUpdate<Self>, +} + +impl<App> Interface<App> +where + App: Application, +{ + pub fn new(available_space: App::Size) -> Self { + let window_cache = App::Cache::create(); + // NOTE: We need to initially clear the interface buffer + let post_update = PostUpdate::new().with_render(); + + Self { + windows: Vec::new(), + window_cache, + available_space, + post_update, + } + } + + pub fn schedule_render(&mut self) { + self.post_update.render(); + } + + pub fn schedule_render_window(&mut self, window_index: usize) { + if window_index < self.windows.len() { + let (_, post_update) = &mut self.windows[window_index]; + post_update.render(); + } + } + + /// The update and render functions take care of merging the window specific + /// flags with the interface wide flags. + fn handle_change_event(post_update: &mut PostUpdate<Self>, window_post_update: &mut PostUpdate<PerWindow>, change_event: ChangeEvent) { + if change_event.contains(ChangeEvent::RENDER_WINDOW) { + window_post_update.render(); + } + + if change_event.contains(ChangeEvent::RESOLVE_WINDOW) { + window_post_update.resolve(); + } + + if change_event.contains(ChangeEvent::RENDER) { + post_update.render(); + } + + if change_event.contains(ChangeEvent::RESOLVE) { + post_update.resolve(); + } + } + + // #[cfg_attr(feature = "debug", procedural::profile("update user interface"))] + pub fn update(&mut self, application: &App, font_loader: App::FontLoader, focus_state: &mut FocusState<App>) -> (bool, bool) { + for (window, post_update) in &mut self.windows { + // #[cfg(feature = "debug")] + // profile_block!("update window"); + + if let Some(change_event) = window.update() { + Self::handle_change_event(&mut self.post_update, post_update, change_event); + } + } + + let mut restore_focus = false; + + for (window_index, (window, post_update)) in self.windows.iter_mut().enumerate() { + if self.post_update.needs_resolve() || post_update.take_resolve() { + // #[cfg(feature = "debug")] + // profile_block!("resolve window"); + + let (_position, previous_size) = window.get_area(); + let kind = window.get_theme_kind(); + let theme = application.get_theme(kind); + + let (window_class, new_position, new_size) = window.resolve(font_loader.clone(), application, theme, self.available_space); + + // should only ever be the last window + if let Some(focused_index) = focus_state.focused_window() + && focused_index == window_index + { + restore_focus = true; + } + + if let Some(window_class) = window_class { + self.window_cache.register_window(window_class, new_position, new_size); + } + + // NOTE: If the window got smaller, we need to re-render the entire interface. + // If it got bigger, we can just draw over the previous frame. + match previous_size.width() > new_size.width() || previous_size.height() > new_size.height() { + true => self.post_update.render(), + false => post_update.render(), + } + } + } + + if restore_focus { + self.restore_focus(focus_state); + } + + if self.post_update.take_resolve() { + self.post_update.render(); + } + + if !self.post_update.needs_render() { + // We profile this block rather than the flag function itself because it calls + // itself recursively + // #[cfg(feature = "debug")] + // profile_block!("flag render windows"); + + self.flag_render_windows(application, 0, None); + } + + let render_interface = self.post_update.needs_render(); + let render_window = self.post_update.needs_render() | self.windows.iter().any(|(_window, post_update)| post_update.needs_render()); + + (render_interface, render_window) + } + + pub fn update_window_size(&mut self, screen_size: App::Size) { + self.available_space = screen_size; + self.post_update.resolve(); + } + + // #[cfg_attr(feature = "debug", procedural::profile("get hovered element"))] + pub fn hovered_element( + &self, + mouse_position: App::Position, + mouse_mode: &App::MouseInputMode, + ) -> (Option<ElementCell<App>>, Option<usize>) { + for (window_index, (window, _)) in self.windows.iter().enumerate().rev() { + match window.hovered_element(mouse_position, mouse_mode) { + HoverInformation::Element(hovered_element) => return (Some(hovered_element), Some(window_index)), + HoverInformation::Hovered => return (None, Some(window_index)), + HoverInformation::Missed => {} + } + } + + (None, None) + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn move_window_to_top(&mut self, window_index: usize) -> usize { + let (window, post_update) = self.windows.remove(window_index); + let new_window_index = self.windows.len(); + + self.windows.push((window, post_update.with_render())); + + new_window_index + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn left_click_element(&mut self, hovered_element: &ElementCell<App>, window_index: usize) -> Vec<ClickAction<App>> { + let (_, post_update) = &mut self.windows[window_index]; + let mut resolve = false; + + let action = hovered_element.borrow_mut().left_click(&mut resolve); // TODO: add same change_event check as for input character ? + + if resolve { + post_update.resolve(); + } + + action + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn right_click_element(&mut self, hovered_element: &ElementCell<App>, window_index: usize) -> Vec<ClickAction<App>> { + let (_, post_update) = &mut self.windows[window_index]; + let mut resolve = false; + + let action = hovered_element.borrow_mut().right_click(&mut resolve); // TODO: add same change_event check as for input character ? + + if resolve { + post_update.resolve(); + } + + action + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn drag_element(&mut self, element: &ElementCell<App>, _window_index: usize, mouse_delta: App::Position) { + //let (_window, post_update) = &mut self.windows[window_index]; + + if let Some(change_event) = element.borrow_mut().drag(mouse_delta) { + // TODO: Use the window post_update here (?) + Self::handle_change_event(&mut self.post_update, &mut PostUpdate::new(), change_event); + } + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn scroll_element(&mut self, element: &ElementCell<App>, window_index: usize, scroll_delta: f32) { + let (_, post_update) = &mut self.windows[window_index]; + + if let Some(change_event) = element.borrow_mut().scroll(scroll_delta) { + Self::handle_change_event(&mut self.post_update, post_update, change_event); + } + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn input_character_element(&mut self, element: &ElementCell<App>, window_index: usize, character: char) -> Vec<ClickAction<App>> { + let (_, post_update) = &mut self.windows[window_index]; + let mut propagated_actions = Vec::new(); + + for action in element.borrow_mut().input_character(character) { + match action { + ClickAction::ChangeEvent(change_event) => Self::handle_change_event(&mut self.post_update, post_update, change_event), + other => propagated_actions.push(other), + } + } + + propagated_actions + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn move_window(&mut self, window_index: usize, offset: App::Position) { + if let Some((window_class, position)) = self.windows[window_index].0.offset(self.available_space, offset) { + self.window_cache.update_position(window_class, position); + } + + self.post_update.render(); + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn resize_window(&mut self, application: &App, window_index: usize, growth: App::Size) { + let (window, post_update) = &mut self.windows[window_index]; + + let (_position, previous_size) = window.get_area(); + let (window_class, new_size) = window.resize(application, self.available_space, growth); + + if !previous_size.is_equal(new_size) { + if let Some(window_class) = window_class { + self.window_cache.update_size(window_class, new_size); + } + + post_update.resolve(); + + if previous_size.width() > new_size.width() || previous_size.height() > new_size.height() { + self.post_update.render(); + } + } + } + + /// This function is solely responsible for making sure that trying to + /// re-render a window with transparency will result in re-rendering the + /// entire interface. This serves as a single point of truth and simplifies + /// the rest of the code. + fn flag_render_windows(&mut self, application: &App, start_index: usize, area: Option<(App::Position, App::Size)>) { + for window_index in start_index..self.windows.len() { + let needs_render = self.windows[window_index].1.needs_render(); + let is_hovering = |(position, scale)| self.windows[window_index].0.hovers_area(position, scale); + + if needs_render || area.map(is_hovering).unwrap_or(false) { + let (position, scale) = { + let (window, post_update) = &mut self.windows[window_index]; + + let kind = window.get_theme_kind(); + let theme = application.get_theme(kind); + + if window.has_transparency(theme) { + self.post_update.render(); + return; + } + + post_update.render(); + window.get_area() + }; + + self.flag_render_windows(application, window_index + 1, Some((position, scale))); + } + } + } + + // #[cfg_attr(feature = "debug", procedural::profile("render user interface"))] + pub fn render( + &mut self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + hovered_element: Option<ElementCell<App>>, + focused_element: Option<ElementCell<App>>, + mouse_mode: &App::MouseInputMode, + ) { + let hovered_element = hovered_element.map(|element| unsafe { &*element.as_ptr() }); + let focused_element = focused_element.map(|element| unsafe { &*element.as_ptr() }); + + for (window, post_update) in &mut self.windows { + if post_update.take_render() || self.post_update.needs_render() { + // #[cfg(feature = "debug")] + // profile_block!("render window"); + + let kind = window.get_theme_kind(); + let theme = application.get_theme(kind); + + window.render( + render_target, + renderer, + application, + theme, + hovered_element, + focused_element, + mouse_mode, + ); + } + } + + self.post_update.take_render(); + } + + // #[cfg_attr(feature = "debug", procedural::profile("check window exists"))] + fn window_exists(&self, window_class: Option<&str>) -> bool { + match window_class { + Some(window_class) => self.windows.iter().any(|window| { + window + .0 + .get_window_class() + .map_or(false, |other_window_class| window_class == other_window_class) + }), + None => false, + } + } + + fn open_new_window(&mut self, focus_state: &mut FocusState<App>, window: Window<App>) { + self.windows.push((window, PostUpdate::new().with_resolve())); + focus_state.set_focused_window(self.windows.len() - 1); + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn open_window(&mut self, application: &App, focus_state: &mut FocusState<App>, prototype_window: &dyn PrototypeWindow<App>) { + if !self.window_exists(prototype_window.window_class()) { + let window = prototype_window.to_window(&self.window_cache, application, self.available_space); + self.open_new_window(focus_state, window); + } + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn open_popup( + &mut self, + element: ElementCell<App>, + position_tracker: Tracker<App::Position>, + size_tracker: Tracker<App::Size>, + window_index: usize, + ) { + let entry = &mut self.windows[window_index]; + entry.0.open_popup(element, position_tracker, size_tracker); + entry.1.resolve(); + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn close_popup(&mut self, window_index: usize) { + let entry = &mut self.windows[window_index]; + entry.0.close_popup(); + entry.1.render(); + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn close_window(&mut self, focus_state: &mut FocusState<App>, window_index: usize) { + let (window, ..) = self.windows.remove(window_index); + self.post_update.render(); + + // drop window in another thread to avoid frame drops when deallocation a large + // amount of elements + std::thread::spawn(move || drop(window)); + + // TODO: only if tab mode + self.restore_focus(focus_state); + } + + pub fn get_window(&self, window_index: usize) -> &Window<App> { + &self.windows[window_index].0 + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn close_window_with_class(&mut self, focus_state: &mut FocusState<App>, window_class: &str) { + let index_from_back = self + .windows + .iter() + .rev() + .map(|(window, ..)| window.get_window_class()) + .position(|class_option| class_option.contains(&window_class)) + .unwrap(); + let index = self.windows.len() - 1 - index_from_back; + + self.close_window(focus_state, index); + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn close_all_windows_except(&mut self, focus_state: &mut FocusState<App>) { + for index in (0..self.windows.len()).rev() { + if self.windows[index] + .0 + .get_window_class() + .map(|class| class != "theme_viewer" && class != "profiler" && class != "network") // HACK: don't hardcode + .unwrap_or(true) + { + self.close_window(focus_state, index); + } + } + } + + // #[cfg_attr(feature = "debug", procedural::profile("get first focused + // element"))] + pub fn first_focused_element(&self, focus_state: &mut FocusState<App>) { + if self.windows.is_empty() { + return; + } + + let window_index = self.windows.len() - 1; + let element = self.windows.last().unwrap().0.first_focused_element(); + + focus_state.set_focused_element(element, window_index); + } + + // #[cfg_attr(feature = "debug", procedural::profile)] + pub fn restore_focus(&self, focus_state: &mut FocusState<App>) { + if self.windows.is_empty() { + return; + } + + let window_index = self.windows.len() - 1; + let element = self.windows.last().unwrap().0.restore_focus(); + + focus_state.set_focused_element(element, window_index); + } +} diff --git a/korangar_interface/src/settings.rs b/korangar_interface/src/settings.rs new file mode 100644 index 00000000..e69de29b diff --git a/korangar_interface/src/state.rs b/korangar_interface/src/state.rs new file mode 100644 index 00000000..94e0dc38 --- /dev/null +++ b/korangar_interface/src/state.rs @@ -0,0 +1,518 @@ +use std::cell::{Ref, RefCell}; +use std::marker::PhantomData; +use std::ops::Not; +use std::rc::Rc; + +use super::ClickAction; +use crate::application::Application; + +/// The state of a value borrowed by [`borrow_mut`](TrackedState::with_mut). +pub enum ValueState<T> { + Mutated(T), + Unchanged(T), +} + +/// The version of the current value inside a [`TrackedState`]. This is used to +/// update [`Remote`]s when the value changes. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct Version(usize); + +impl Version { + /// Get a [`Version`] from a [`usize`]. + pub fn from_raw(version: usize) -> Self { + Self(version) + } + + /// Bump the version by 1. + fn bump(&mut self) { + self.0 = self.0.wrapping_add(1); + } +} + +/// Shared state that keeps track of mutations to the inner value. It can create +/// [`Remote`]s to listen to state changes. +pub trait TrackedState<Value>: Clone { + type RemoteType: Remote<Value> + 'static; + + /// Set the inner value. + fn set(&mut self, value: Value); + + /// Get an immutable reference to the inner value. + fn get(&self) -> Ref<'_, Value>; + + /// Get the current [`Version`]. + fn get_version(&self) -> Version; + + /// Work on a mutable reference of the inner value. The provided closure has + /// to report back whether or not the value inside was mutated. If any state + /// change occurred, the closure *must* return [`ValueState::Mutated`]. + /// + /// NOTE: It is imperative that the correct state is + /// returned. Returning [`ValueState::Mutated`] when no modification was + /// done might result in unnecessary updates. Similarly, returning + /// [`ValueState::Unchanged`] when the value was mutated will result in + /// no update at all. + fn with_mut<Closure, Return>(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut Value) -> ValueState<Return>; + + /// Advance the version of the state without modifying the inner value. + fn update(&mut self); + + /// Create a new [`Remote`]. + fn new_remote(&self) -> Self::RemoteType; +} + +/// Extension trait to mutate the inner value of a [`TrackedState`]. +pub trait TrackedStateExt<Value> { + fn mutate<Closure, Return>(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut Value) -> Return; +} + +impl<T, Value> TrackedStateExt<Value> for T +where + T: TrackedState<Value>, +{ + fn mutate<Closure, Return>(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut Value) -> Return, + { + self.with_mut(|value| ValueState::Mutated(closure(value))) + } +} + +#[derive(Default)] +struct InnerState<Value> { + value: Value, + version: Version, +} + +impl<Value> InnerState<Value> { + pub fn new(value: Value) -> Self { + Self { + value, + version: Version::default(), + } + } +} + +#[derive(Default)] +pub struct PlainTrackedState<Value>(Rc<RefCell<InnerState<Value>>>); + +impl<Value> PlainTrackedState<Value> { + pub fn new(value: Value) -> PlainTrackedState<Value> { + Self(Rc::new(RefCell::new(InnerState::new(value)))) + } + + pub fn mapped<As, F>(&self, mapping: F) -> MappedTrackedState<Value, As, F> + where + F: Fn(&Value) -> &As, + { + MappedTrackedState { + state: self.clone(), + mapping, + marker: PhantomData, + } + } + + pub fn mapped_remote<As, F>(&self, mapping: F) -> MappedRemote<Value, As, F> + where + F: Fn(&Value) -> &As, + { + let tracked_state = self.mapped(mapping); + + MappedRemote { + tracked_state, + version: self.get_version(), + } + } + + pub fn foo_test(&self) { + println!("Weak count: {}", Rc::weak_count(&self.0)); + println!("Strong count: {}", Rc::strong_count(&self.0)); + } +} + +impl<Value> Clone for PlainTrackedState<Value> { + fn clone(&self) -> Self { + Self(Rc::clone(&self.0)) + } +} + +impl<Value> TrackedState<Value> for PlainTrackedState<Value> +where + Value: 'static, +{ + type RemoteType = PlainRemote<Value>; + + fn set(&mut self, value: Value) { + let mut inner = self.0.borrow_mut(); + inner.value = value; + inner.version.bump(); + } + + fn get(&self) -> Ref<'_, Value> { + Ref::map(self.0.borrow(), |inner| &inner.value) + } + + fn get_version(&self) -> Version { + self.0.borrow().version + } + + fn with_mut<Closure, Return>(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut Value) -> ValueState<Return>, + { + let mut inner = self.0.borrow_mut(); + + match closure(&mut inner.value) { + ValueState::Mutated(return_value) => { + inner.version.bump(); + return_value + } + ValueState::Unchanged(return_value) => return_value, + } + } + + fn update(&mut self) { + self.0.borrow_mut().version.bump(); + } + + fn new_remote(&self) -> Self::RemoteType { + let tracked_state = self.clone(); + let version = self.get_version(); + + PlainRemote { tracked_state, version } + } +} + +pub struct MappedTrackedState<Value, As, F> +where + Value: 'static, + As: 'static, + F: Fn(&Value) -> &As + 'static, +{ + state: PlainTrackedState<Value>, + mapping: F, + marker: PhantomData<As>, +} + +impl<Value, As, F> Clone for MappedTrackedState<Value, As, F> +where + F: Clone + Fn(&Value) -> &As, +{ + fn clone(&self) -> Self { + Self { + state: self.state.clone(), + mapping: self.mapping.clone(), + marker: PhantomData, + } + } +} + +impl<Value, As, F> TrackedState<As> for MappedTrackedState<Value, As, F> +where + Value: 'static, + As: 'static, + F: Clone + Fn(&Value) -> &As + 'static, +{ + type RemoteType = MappedRemote<Value, As, F>; + + fn set(&mut self, value: As) { + let mut inner = self.state.0.borrow_mut(); + let mapped = (self.mapping)(&inner.value); + // SAFETY: This operation _should_ be perfectly safe, since we just casted from + // the mutable reference to an immutable one. This bit of unsafety makes + // the creation of mappings much easier. + #[allow(mutable_transmutes)] + let mapped = unsafe { std::mem::transmute::<&As, &mut As>(mapped) }; + + *mapped = value; + inner.version.bump(); + } + + fn get(&self) -> Ref<'_, As> { + Ref::map(self.state.get(), &self.mapping) + } + + fn get_version(&self) -> Version { + self.state.get_version() + } + + fn with_mut<Closure, Return>(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut As) -> ValueState<Return>, + { + let mut inner = self.state.0.borrow_mut(); + let mapped = (self.mapping)(&inner.value); + // SAFETY: This operation _should_ be perfectly safe, since we just casted from + // the mutable reference to an immutable one. This bit of unsafety makes + // the creation of mappings much easier. + #[allow(mutable_transmutes)] + let mapped = unsafe { std::mem::transmute::<&As, &mut As>(mapped) }; + + match closure(mapped) { + ValueState::Mutated(return_value) => { + inner.version.bump(); + return_value + } + ValueState::Unchanged(return_value) => return_value, + } + } + + fn update(&mut self) { + self.state.update(); + } + + fn new_remote(&self) -> Self::RemoteType { + MappedRemote { + tracked_state: self.clone(), + version: self.state.get_version(), + } + } +} + +pub trait TrackedStateClone<Value>: TrackedState<Value> { + fn cloned(&self) -> Value; +} + +impl<T, Value> TrackedStateClone<Value> for T +where + T: TrackedState<Value>, + Value: Clone, +{ + fn cloned(&self) -> Value { + self.get().clone() + } +} + +pub trait TrackedStateTake<Value>: TrackedState<Value> { + fn take(&mut self) -> Value; +} + +impl<T, Value> TrackedStateTake<Value> for T +where + T: TrackedState<Value>, + Value: Default, +{ + fn take(&mut self) -> Value { + let mut taken_value = Value::default(); + + self.with_mut(|value| { + std::mem::swap(&mut taken_value, value); + ValueState::Mutated(()) + }); + + taken_value + } +} + +pub trait TrackedStateVec<Value> { + fn clear(&mut self); + + fn push(&mut self, item: Value); + + fn retain<F>(&mut self, f: F) + where + F: FnMut(&Value) -> bool; + + fn len(&self) -> usize; +} + +impl<T, Value> TrackedStateVec<Value> for T +where + T: TrackedState<Vec<Value>>, +{ + fn clear(&mut self) { + self.with_mut(|value| { + value.clear(); + ValueState::Mutated(()) + }); + } + + fn push(&mut self, item: Value) { + self.with_mut(|value| { + value.push(item); + ValueState::Mutated(()) + }); + } + + fn retain<F>(&mut self, mut f: F) + where + F: FnMut(&Value) -> bool, + { + self.with_mut(|value| { + let previous_length = value.len(); + + value.retain_mut(|element| f(element)); + + let new_length = value.len(); + + match new_length < previous_length { + true => ValueState::Mutated(()), + false => ValueState::Unchanged(()), + } + }); + } + + fn len(&self) -> usize { + self.get().len() + } +} + +pub trait TrackedStateBinary<Value>: TrackedState<Value> { + fn toggle(&mut self); + + fn selector(&self) -> impl Fn() -> Value + 'static; + + fn toggle_action<App>(&self) -> impl FnMut() -> Vec<ClickAction<App>> + 'static + where + App: Application; +} + +impl<T, Value> TrackedStateBinary<Value> for T +where + T: TrackedState<Value> + 'static + Clone, // TODO: Move this clone bound elsewhere + Value: Not<Output = Value> + Copy, +{ + fn toggle(&mut self) { + self.with_mut(|value| { + *value = !*value; + ValueState::Mutated(()) + }); + } + + fn selector(&self) -> impl Fn() -> Value + 'static { + let cloned = self.clone(); + move || cloned.cloned() + } + + fn toggle_action<App>(&self) -> impl FnMut() -> Vec<ClickAction<App>> + 'static + where + App: Application, + { + let mut cloned = (*self).clone(); + move || { + cloned.toggle(); + Vec::new() + } + } +} + +pub trait Remote<Value> { + type State; + + fn clone_state(&self) -> Self::State; + + fn get(&self) -> Ref<'_, Value>; + + fn consume_changed(&mut self) -> bool; +} + +pub trait RemoteClone<Value>: Remote<Value> { + fn cloned(&self) -> Value; +} + +impl<T, Value> RemoteClone<Value> for T +where + T: Remote<Value>, + Value: Clone, +{ + fn cloned(&self) -> Value { + self.get().clone() + } +} + +pub struct PlainRemote<Value> { + tracked_state: PlainTrackedState<Value>, + version: Version, +} + +impl<Value> PlainRemote<Value> +where + Value: 'static, +{ + pub fn new(value: Value) -> PlainRemote<Value> { + PlainTrackedState::new(value).new_remote() + } +} + +impl<Value> Remote<Value> for PlainRemote<Value> +where + Value: 'static, +{ + type State = PlainTrackedState<Value>; + + fn clone_state(&self) -> PlainTrackedState<Value> { + self.tracked_state.clone() + } + + fn get(&self) -> Ref<'_, Value> { + self.tracked_state.get() + } + + fn consume_changed(&mut self) -> bool { + let version = self.tracked_state.get_version(); + let changed = self.version != version; + self.version = version; + + changed + } +} + +impl<Value> Clone for PlainRemote<Value> { + fn clone(&self) -> Self { + let tracked_state = self.tracked_state.clone(); + let version = self.version; + + Self { tracked_state, version } + } +} + +pub struct MappedRemote<Value, As, F> +where + Value: 'static, + As: 'static, + F: Fn(&Value) -> &As + 'static, +{ + tracked_state: MappedTrackedState<Value, As, F>, + version: Version, +} + +impl<Value, As, F> Remote<As> for MappedRemote<Value, As, F> +where + Value: 'static, + As: 'static, + F: Clone + Fn(&Value) -> &As + 'static, +{ + type State = MappedTrackedState<Value, As, F>; + + fn clone_state(&self) -> Self::State { + self.tracked_state.clone() + } + + fn get(&self) -> Ref<'_, As> { + self.tracked_state.get() + } + + fn consume_changed(&mut self) -> bool { + let version = self.tracked_state.get_version(); + let changed = self.version != version; + self.version = version; + + changed + } +} + +impl<Value, As, F> Clone for MappedRemote<Value, As, F> +where + F: Clone + Fn(&Value) -> &As, +{ + fn clone(&self) -> Self { + let tracked_state = self.tracked_state.clone(); + let version = self.version; + + Self { tracked_state, version } + } +} diff --git a/korangar_interface/src/theme.rs b/korangar_interface/src/theme.rs new file mode 100644 index 00000000..af130e6d --- /dev/null +++ b/korangar_interface/src/theme.rs @@ -0,0 +1,215 @@ +use crate::application::Application; +use crate::layout::{DimensionBound, SizeBound}; + +pub trait ButtonTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn hovered_background_color(&self) -> App::Color; + fn disabled_background_color(&self) -> App::Color; + fn foreground_color(&self) -> App::Color; + fn hovered_foreground_color(&self) -> App::Color; + fn disabled_foreground_color(&self) -> App::Color; + fn debug_foreground_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn icon_offset(&self) -> App::Position; + fn icon_size(&self) -> App::Size; + fn icon_text_offset(&self) -> App::Position; + fn text_offset(&self) -> App::Position; + fn font_size(&self) -> App::FontSize; + fn height_bound(&self) -> DimensionBound; +} + +pub trait WindowTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn title_background_color(&self) -> App::Color; + fn foreground_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn title_corner_radius(&self) -> App::CornerRadius; + fn border_size(&self) -> App::Size; + fn text_offset(&self) -> App::Position; + fn gaps(&self) -> App::Size; + fn font_size(&self) -> App::FontSize; + fn title_height(&self) -> DimensionBound; +} + +pub trait ExpandableTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn second_background_color(&self) -> App::Color; + fn foreground_color(&self) -> App::Color; + fn hovered_foreground_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn border_size(&self) -> App::Size; + fn element_offset(&self) -> App::Position; + fn icon_offset(&self) -> App::Position; + fn icon_size(&self) -> App::Size; + fn text_offset(&self) -> App::Position; + fn gaps(&self) -> App::Size; + fn font_size(&self) -> App::FontSize; +} + +pub trait LabelTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn foreground_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn text_offset(&self) -> App::Position; + fn font_size(&self) -> App::FontSize; + fn size_bound(&self) -> SizeBound; +} + +pub trait ValueTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn hovered_background_color(&self) -> App::Color; + fn foreground_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn text_offset(&self) -> App::Position; + fn font_size(&self) -> App::FontSize; + fn size_bound(&self) -> SizeBound; +} + +pub trait CloseButtonTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn hovered_background_color(&self) -> App::Color; + fn foreground_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn text_offset(&self) -> App::Position; + fn font_size(&self) -> App::FontSize; + fn size_bound(&self) -> SizeBound; +} + +pub trait OverlayTheme<App> +where + App: Application, +{ + fn foreground_color(&self) -> App::Color; + fn text_offset(&self) -> App::Position; + fn font_size(&self) -> App::FontSize; +} + +pub trait SliderTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn rail_color(&self) -> App::Color; + fn knob_color(&self) -> App::Color; + fn size_bound(&self) -> SizeBound; +} + +pub trait InputTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn hovered_background_color(&self) -> App::Color; + fn focused_background_color(&self) -> App::Color; + fn text_color(&self) -> App::Color; + fn ghost_text_color(&self) -> App::Color; + fn focused_text_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn font_size(&self) -> App::FontSize; + fn text_offset(&self) -> App::Position; + fn cursor_offset(&self) -> f32; + fn cursor_width(&self) -> f32; + fn height_bound(&self) -> DimensionBound; +} + +pub trait ChatTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn font_size(&self) -> App::FontSize; +} + +pub trait CursorTheme<App> +where + App: Application, +{ + fn color(&self) -> App::Color; +} + +pub trait ProfilerTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn corner_radius(&self) -> App::CornerRadius; + fn line_color(&self) -> App::Color; + fn line_width(&self) -> f32; + fn bar_height(&self) -> f32; + fn bar_gap(&self) -> App::Size; + fn bar_corner_radius(&self) -> App::CornerRadius; + fn bar_text_color(&self) -> App::Color; + fn bar_text_size(&self) -> f32; + fn bar_text_offset(&self) -> App::Position; + fn distance_text_size(&self) -> f32; + fn distance_text_offset(&self) -> f32; +} + +pub trait StatusBarTheme<App> +where + App: Application, +{ + fn background_color(&self) -> App::Color; + fn player_health_color(&self) -> App::Color; + fn enemy_health_color(&self) -> App::Color; + fn spell_point_color(&self) -> App::Color; + fn activity_point_color(&self) -> App::Color; + fn player_bar_width(&self) -> f32; + fn enemy_bar_width(&self) -> f32; + fn health_height(&self) -> f32; + fn enemy_health_height(&self) -> f32; + fn spell_point_height(&self) -> f32; + fn activity_point_height(&self) -> f32; + fn border_size(&self) -> App::Size; + fn gap(&self) -> f32; +} + +pub trait IndicatorTheme<App> +where + App: Application, +{ + fn walking(&self) -> App::Color; +} + +pub trait InterfaceTheme { + type Settings: Application; + type Button: ButtonTheme<Self::Settings>; + type Window: WindowTheme<Self::Settings>; + type Expandable: ExpandableTheme<Self::Settings>; + type Label: LabelTheme<Self::Settings>; + type Value: ValueTheme<Self::Settings>; + type CloseButton: CloseButtonTheme<Self::Settings>; + type Slider: SliderTheme<Self::Settings>; + type Input: InputTheme<Self::Settings>; + type Profiler: ProfilerTheme<Self::Settings>; + type Chat: ChatTheme<Self::Settings>; + + fn button(&self) -> &Self::Button; + fn window(&self) -> &Self::Window; + fn expandable(&self) -> &Self::Expandable; + fn label(&self) -> &Self::Label; + fn value(&self) -> &Self::Value; + fn close_button(&self) -> &Self::CloseButton; + fn slider(&self) -> &Self::Slider; + fn input(&self) -> &Self::Input; + fn profiler(&self) -> &Self::Profiler; + fn chat(&self) -> &Self::Chat; +} diff --git a/src/interface/windows/builder.rs b/korangar_interface/src/windows/builder.rs similarity index 53% rename from src/interface/windows/builder.rs rename to korangar_interface/src/windows/builder.rs index 4401d6a3..02df6fc6 100644 --- a/src/interface/windows/builder.rs +++ b/korangar_interface/src/windows/builder.rs @@ -1,7 +1,12 @@ -use procedural::dimension_bound; +use std::marker::PhantomData; +use std::rc::Rc; -use crate::interface::builder::{Set, Unset}; -use crate::interface::*; +use super::Window; +use crate::application::{Application, PartialSizeTraitExt, PositionTraitExt, SizeTraitExt, WindowCache}; +use crate::builder::{Set, Unset}; +use crate::elements::{CloseButtonBuilder, Container, DragButtonBuilder, ElementCell, ElementWrap}; +use crate::layout::{Dimension, DimensionBound, SizeBound}; +use crate::ColorSelector; /// Type state [`Window`] builder. This builder utilizes the type system to /// prevent calling the same method multiple times, calling @@ -9,18 +14,24 @@ use crate::interface::*; /// enforce some conditional logic. Namely, the `closable` method can only be /// called if the window has a title. #[must_use = "`build` needs to be called"] -pub struct WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> { +pub struct WindowBuilder<App, Title, Closable, Class, Size, Elements, Background, Theme> +where + App: Application, +{ title: Option<String>, closable: bool, class: Option<String>, - size_bound: SIZE, - elements: ELEMENTS, - background_color: Option<ColorSelector>, - theme_kind: ThemeKind, - marker: PhantomData<(TITLE, CLOSABLE, CLASS, BACKGROUND, THEME)>, + size_bound: Size, + elements: Elements, + background_color: Option<ColorSelector<App>>, + theme_kind: App::ThemeKind, + marker: PhantomData<(Title, Closable, Class, Background, Theme)>, } -impl WindowBuilder<Unset, Unset, Unset, Unset, Unset, Unset, Unset> { +impl<App> WindowBuilder<App, Unset, Unset, Unset, Unset, Unset, Unset, Unset> +where + App: Application, +{ pub fn new() -> Self { Self { title: None, @@ -29,14 +40,17 @@ impl WindowBuilder<Unset, Unset, Unset, Unset, Unset, Unset, Unset> { size_bound: Unset, elements: Unset, background_color: None, - theme_kind: ThemeKind::default(), + theme_kind: App::ThemeKind::default(), marker: PhantomData, } } } -impl<CLASS, CLOSABLE, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<Unset, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> { - pub fn with_title(self, title: impl Into<String>) -> WindowBuilder<Set, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> { +impl<App, Class, Closable, Size, Elements, Background, Theme> WindowBuilder<App, Unset, Closable, Class, Size, Elements, Background, Theme> +where + App: Application, +{ + pub fn with_title(self, title: impl Into<String>) -> WindowBuilder<App, Set, Closable, Class, Size, Elements, Background, Theme> { WindowBuilder { title: Some(title.into()), marker: PhantomData, @@ -45,10 +59,13 @@ impl<CLASS, CLOSABLE, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<Unset, CL } } -impl<CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<Set, Unset, CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> { +impl<App, Class, Size, Elements, Background, Theme> WindowBuilder<App, Set, Unset, Class, Size, Elements, Background, Theme> +where + App: Application, +{ /// NOTE: This function is only available if /// [`with_title`](Self::with_title) has been called on the builder. - pub fn closable(self) -> WindowBuilder<Set, Set, CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> { + pub fn closable(self) -> WindowBuilder<App, Set, Set, Class, Size, Elements, Background, Theme> { WindowBuilder { closable: true, marker: PhantomData, @@ -57,8 +74,11 @@ impl<CLASS, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<Set, Unset, CLASS, } } -impl<TITLE, CLOSABLE, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, Unset, SIZE, ELEMENTS, BACKGROUND, THEME> { - pub fn with_class(self, class: impl Into<String>) -> WindowBuilder<TITLE, CLOSABLE, Set, SIZE, ELEMENTS, BACKGROUND, THEME> { +impl<App, Title, Closable, Size, Elements, Background, Theme> WindowBuilder<App, Title, Closable, Unset, Size, Elements, Background, Theme> +where + App: Application, +{ + pub fn with_class(self, class: impl Into<String>) -> WindowBuilder<App, Title, Closable, Set, Size, Elements, Background, Theme> { WindowBuilder { class: Some(class.into()), marker: PhantomData, @@ -67,8 +87,11 @@ impl<TITLE, CLOSABLE, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<TITLE, CL } } -impl<TITLE, CLOSABLE, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, Unset, SIZE, ELEMENTS, BACKGROUND, THEME> { - pub fn with_class_option(self, class: Option<String>) -> WindowBuilder<TITLE, CLOSABLE, Set, SIZE, ELEMENTS, BACKGROUND, THEME> { +impl<App, Title, Closable, Size, Elements, Background, Theme> WindowBuilder<App, Title, Closable, Unset, Size, Elements, Background, Theme> +where + App: Application, +{ + pub fn with_class_option(self, class: Option<String>) -> WindowBuilder<App, Title, Closable, Set, Size, Elements, Background, Theme> { WindowBuilder { class, marker: PhantomData, @@ -77,26 +100,39 @@ impl<TITLE, CLOSABLE, SIZE, ELEMENTS, BACKGROUND, THEME> WindowBuilder<TITLE, CL } } -impl<TITLE, CLOSABLE, CLASS, ELEMENTS, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, CLASS, Unset, ELEMENTS, BACKGROUND, THEME> { - pub fn with_size_bound(self, size_bound: SizeBound) -> WindowBuilder<TITLE, CLOSABLE, CLASS, SizeBound, ELEMENTS, BACKGROUND, THEME> { +impl<App, Title, Closable, Class, Elements, Background, Theme> + WindowBuilder<App, Title, Closable, Class, Unset, Elements, Background, Theme> +where + App: Application, +{ + pub fn with_size_bound( + self, + size_bound: SizeBound, + ) -> WindowBuilder<App, Title, Closable, Class, SizeBound, Elements, Background, Theme> { WindowBuilder { size_bound, ..self } } } -impl<TITLE, CLOSABLE, CLASS, SIZE, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, Unset, BACKGROUND, THEME> { +impl<App, Title, Closable, Class, Size, Background, Theme> WindowBuilder<App, Title, Closable, Class, Size, Unset, Background, Theme> +where + App: Application, +{ pub fn with_elements( self, - elements: Vec<ElementCell>, - ) -> WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, Vec<ElementCell>, BACKGROUND, THEME> { + elements: Vec<ElementCell<App>>, + ) -> WindowBuilder<App, Title, Closable, Class, Size, Vec<ElementCell<App>>, Background, Theme> { WindowBuilder { elements, ..self } } } -impl<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, THEME> WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, Unset, THEME> { +impl<App, Title, Closable, Class, Size, Elements, Theme> WindowBuilder<App, Title, Closable, Class, Size, Elements, Unset, Theme> +where + App: Application, +{ pub fn with_background_color( self, - background_color: ColorSelector, - ) -> WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, Set, THEME> { + background_color: ColorSelector<App>, + ) -> WindowBuilder<App, Title, Closable, Class, Size, Elements, Set, Theme> { WindowBuilder { background_color: Some(background_color), marker: PhantomData, @@ -105,8 +141,14 @@ impl<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, THEME> WindowBuilder<TITLE, CLOSABL } } -impl<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND> WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND, Unset> { - pub fn with_theme_kind(self, theme_kind: ThemeKind) -> WindowBuilder<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND, Set> { +impl<App, Title, Closable, Class, Size, Elements, Background> WindowBuilder<App, Title, Closable, Class, Size, Elements, Background, Unset> +where + App: Application, +{ + pub fn with_theme_kind( + self, + theme_kind: App::ThemeKind, + ) -> WindowBuilder<App, Title, Closable, Class, Size, Elements, Background, Set> { WindowBuilder { theme_kind, marker: PhantomData, @@ -115,13 +157,17 @@ impl<TITLE, CLOSABLE, CLASS, SIZE, ELEMENTS, BACKGROUND> WindowBuilder<TITLE, CL } } -impl<TITLE, CLOSABLE, CLASS, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, CLASS, SizeBound, Vec<ElementCell>, BACKGROUND, THEME> { +impl<App, Title, Closable, Class, Background, Theme> + WindowBuilder<App, Title, Closable, Class, SizeBound, Vec<ElementCell<App>>, Background, Theme> +where + App: Application, +{ /// Take the builder and turn it into a [`Window`]. /// /// NOTE: This method is only available if /// [`with_size_bound`](Self::with_size_bound) and /// [`with_elements`](Self::with_elements) have been called on the builder. - pub fn build(self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { + pub fn build(self, window_cache: &App::Cache, application: &App, available_space: App::Size) -> Window<App> { let Self { title, closable, @@ -139,9 +185,18 @@ impl<TITLE, CLOSABLE, CLASS, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, C } if let Some(title) = title { + // FIX: Any bound will never work properly, use a different way of allocating. let width_bound = match closable { - true => dimension_bound!(70%), - false => dimension_bound!(!), + true => DimensionBound { + size: Dimension::Relative(70.0), + minimum_size: None, + maximum_size: None, + }, + false => DimensionBound { + size: Dimension::Remaining, + minimum_size: None, + maximum_size: None, + }, }; let drag_button = DragButtonBuilder::new() @@ -178,16 +233,16 @@ impl<TITLE, CLOSABLE, CLASS, BACKGROUND, THEME> WindowBuilder<TITLE, CLOSABLE, C .unzip(); let size = cached_size - .map(|size| size_bound.validated_window_size(size, available_space, interface_settings.scaling.get())) + .map(|size| size_bound.validated_window_size(size, available_space, application.get_scaling())) .unwrap_or_else(|| { size_bound - .resolve_window(available_space, available_space, interface_settings.scaling.get()) + .resolve_window::<App::PartialSize>(available_space, available_space, application.get_scaling()) .finalize_or(0.0) }); let position = cached_position .map(|position| size_bound.validated_position(position, size, available_space)) - .unwrap_or(ScreenPosition::from_size((available_space - size) / 2.0)); + .unwrap_or(App::Position::from_size(available_space.shrink(size).halved())); Window { window_class: class, diff --git a/korangar_interface/src/windows/mod.rs b/korangar_interface/src/windows/mod.rs new file mode 100644 index 00000000..d9bbe844 --- /dev/null +++ b/korangar_interface/src/windows/mod.rs @@ -0,0 +1,286 @@ +mod builder; +mod prototype; + +use std::rc::Rc; + +pub use self::builder::WindowBuilder; +pub use self::prototype::PrototypeWindow; +use crate::application::{Application, ClipTrait, ColorTrait, InterfaceRenderer, PositionTrait, PositionTraitExt, SizeTrait, SizeTraitExt}; +use crate::elements::{Element, ElementCell, Focus}; +use crate::event::{ChangeEvent, HoverInformation}; +use crate::layout::{Dimension, PlacementResolver, SizeBound}; +use crate::theme::{InterfaceTheme, WindowTheme}; +use crate::{ColorSelector, Tracker}; + +pub struct Window<App> +where + App: Application, +{ + window_class: Option<String>, + position: App::Position, + size_bound: SizeBound, + size: App::Size, + elements: Vec<ElementCell<App>>, + popup_element: Option<(ElementCell<App>, Tracker<App::Position>, Tracker<App::Size>)>, + closable: bool, + background_color: Option<ColorSelector<App>>, + theme_kind: App::ThemeKind, +} + +impl<App> Window<App> +where + App: Application, +{ + pub fn get_window_class(&self) -> Option<&str> { + self.window_class.as_deref() + } + + fn get_background_color(&self, theme: &App::Theme) -> App::Color { + self.background_color + .as_ref() + .map(|closure| closure(theme)) + .unwrap_or(theme.window().background_color()) + } + + pub fn has_transparency(&self, theme: &App::Theme) -> bool { + self.get_background_color(theme).is_transparent() + } + + pub fn is_closable(&self) -> bool { + self.closable + } + + pub fn get_theme_kind(&self) -> &App::ThemeKind { + &self.theme_kind + } + + pub fn resolve( + &mut self, + font_loader: App::FontLoader, + application: &App, + theme: &App::Theme, + available_space: App::Size, + ) -> (Option<&str>, App::Position, App::Size) { + let mut placement_resolver = PlacementResolver::new( + font_loader.clone(), + available_space, + self.size, + &self.size_bound, + theme.window().border_size(), + theme.window().gaps(), + application.get_scaling(), + ); + + self.elements + .iter() + .for_each(|element| element.borrow_mut().resolve(&mut placement_resolver, application, theme)); + + if self.size_bound.height.is_flexible() { + let parent_limits = placement_resolver.get_parent_limits(); + let final_height = theme.window().border_size().height() + placement_resolver.final_height(); + + let final_height = self.size_bound.validated_height( + final_height, + available_space.height().into(), + available_space.height().into(), + &parent_limits, + application.get_scaling(), + ); + + self.size = App::Size::new(self.size.width(), final_height); + self.validate_size(application, available_space); + } + + self.validate_position(available_space); + + if let Some((popup, _, size_tracker)) = &self.popup_element { + let size = size_tracker().unwrap(); // FIX: Don't unwrap obviously + + let size_bound = SizeBound { + minimum_height: Some(Dimension::Absolute(0.0)), + maximum_height: Some(Dimension::Absolute(250.0)), + ..SizeBound::only_height(Dimension::Flexible) + }; + + let mut placement_resolver = PlacementResolver::new( + font_loader, + available_space, + // TODO: 250 is an arbitrary limitation. This should be replaced with a value based + // on some reasoning. + App::Size::new(size.width(), 250.0), + &size_bound, + App::Size::zero(), //theme.window.border_size.get(), // TODO: Popup + App::Size::zero(), //theme.window.gaps.get(), // TODO: Popup + application.get_scaling(), + ); + + popup.borrow_mut().resolve(&mut placement_resolver, application, theme); + }; + + (self.window_class.as_deref(), self.position, self.size) + } + + pub fn update(&mut self) -> Option<ChangeEvent> { + self.elements + .iter_mut() + .map(|element| element.borrow_mut().update()) + .fold(None, |current, other| { + current.zip_with(other, ChangeEvent::union).or(current).or(other) + }) + } + + pub fn first_focused_element(&self) -> Option<ElementCell<App>> { + let element_cell = self.elements[0].clone(); + self.elements[0].borrow().focus_next(element_cell, None, Focus::downwards()) + } + + pub fn restore_focus(&self) -> Option<ElementCell<App>> { + self.elements[0].borrow().restore_focus(self.elements[0].clone()) + } + + pub fn hovered_element(&self, mouse_position: App::Position, mouse_mode: &App::MouseInputMode) -> HoverInformation<App> { + let absolute_position = mouse_position.relative_to(self.position); + + if let Some((popup, position_tracker, _)) = &self.popup_element { + let position = position_tracker().unwrap(); // FIX: Don't unwrap obviously + let position = mouse_position.relative_to(position); + + match popup.borrow().hovered_element(position, mouse_mode) { + HoverInformation::Hovered => return HoverInformation::Element(popup.clone()), + HoverInformation::Missed => {} + hover_information => return hover_information, + } + } + + if absolute_position.left() >= 0.0 + && absolute_position.top() >= 0.0 + && absolute_position.left() <= self.size.width() + && absolute_position.top() <= self.size.height() + { + for element in &self.elements { + match element.borrow().hovered_element(absolute_position, mouse_mode) { + HoverInformation::Hovered => return HoverInformation::Element(element.clone()), + HoverInformation::Missed => {} + hover_information => return hover_information, + } + } + + return HoverInformation::Hovered; + } + + HoverInformation::Missed + } + + pub fn get_area(&self) -> (App::Position, App::Size) { + (self.position, self.size) + } + + pub fn hovers_area(&self, position: App::Position, size: App::Size) -> bool { + let self_combined = self.position.offset(self.size); + let area_combined = position.offset(size); + + self_combined.left() > position.left() + && self.position.left() < area_combined.left() + && self_combined.top() > position.top() + && self.position.top() < area_combined.top() + } + + pub fn offset(&mut self, available_space: App::Size, offset: App::Position) -> Option<(&str, App::Position)> { + self.position = self.position.combined(offset); + self.validate_position(available_space); + self.window_class + .as_ref() + .map(|window_class| (window_class.as_str(), self.position)) + } + + fn validate_position(&mut self, available_space: App::Size) { + self.position = self.size_bound.validated_position(self.position, self.size, available_space); + } + + pub fn resize(&mut self, application: &App, available_space: App::Size, growth: App::Size) -> (Option<&str>, App::Size) { + self.size = self.size.grow(growth); + self.validate_size(application, available_space); + (self.window_class.as_deref(), self.size) + } + + fn validate_size(&mut self, application: &App, available_space: App::Size) { + self.size = self + .size_bound + .validated_window_size(self.size, available_space, application.get_scaling()); + } + + pub fn open_popup(&mut self, element: ElementCell<App>, position_tracker: Tracker<App::Position>, size_tracker: Tracker<App::Size>) { + // NOTE: Very important to link back + let weak_element = Rc::downgrade(&element); + element.borrow_mut().link_back(weak_element, None); + + self.popup_element = Some((element, position_tracker, size_tracker)); + } + + pub fn close_popup(&mut self) { + self.popup_element = None; + } + + pub fn render( + &self, + render_target: &mut <App::Renderer as InterfaceRenderer<App>>::Target, + renderer: &App::Renderer, + application: &App, + theme: &App::Theme, + hovered_element: Option<&dyn Element<App>>, + focused_element: Option<&dyn Element<App>>, + mouse_mode: &App::MouseInputMode, + ) { + let screen_clip = App::Clip::new( + self.position.left(), + self.position.top(), + self.position.left() + self.size.width(), + self.position.top() + self.size.height(), + ); + + renderer.render_rectangle( + render_target, + self.position, + self.size, + screen_clip, + theme.window().corner_radius(), + self.get_background_color(theme), + ); + + self.elements.iter().for_each(|element| { + element.borrow().render( + render_target, + renderer, + application, + theme, + self.position, + screen_clip, + hovered_element, + focused_element, + mouse_mode, + false, + ) + }); + + if let Some((popup, position_tracker, _)) = &self.popup_element { + let position = position_tracker().unwrap(); // FIX: Don't unwrap obviously + + popup.borrow().render( + render_target, + renderer, + application, + theme, + position, + screen_clip, + hovered_element, + focused_element, + mouse_mode, + false, + ); + }; + } +} + +// Needed so that we can deallocate Window in another thread. +unsafe impl<App> Send for Window<App> where App: Application {} +unsafe impl<App> Sync for Window<App> where App: Application {} diff --git a/korangar_interface/src/windows/prototype.rs b/korangar_interface/src/windows/prototype.rs new file mode 100644 index 00000000..0871f49f --- /dev/null +++ b/korangar_interface/src/windows/prototype.rs @@ -0,0 +1,13 @@ +use crate::application::Application; +use crate::Window; + +pub trait PrototypeWindow<App> +where + App: Application, +{ + fn window_class(&self) -> Option<&str> { + None + } + + fn to_window(&self, window_cache: &App::Cache, application: &App, available_space: App::Size) -> Window<App>; +} diff --git a/procedural/Cargo.toml b/korangar_procedural/Cargo.toml similarity index 85% rename from procedural/Cargo.toml rename to korangar_procedural/Cargo.toml index d4f1dfea..bab18970 100644 --- a/procedural/Cargo.toml +++ b/korangar_procedural/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "procedural" +name = "korangar_procedural" version = "0.1.0" edition = "2021" diff --git a/procedural/README.md b/korangar_procedural/README.md similarity index 100% rename from procedural/README.md rename to korangar_procedural/README.md diff --git a/procedural/src/bound.rs b/korangar_procedural/src/bound.rs similarity index 91% rename from procedural/src/bound.rs rename to korangar_procedural/src/bound.rs index 58ab111b..d8507a18 100644 --- a/procedural/src/bound.rs +++ b/korangar_procedural/src/bound.rs @@ -16,17 +16,17 @@ impl syn::parse::Parse for Dimension { // remove the ',' input.parse::<Punct>()?; - quote!(crate::interface::Dimension::Remaining) + quote!(korangar_interface::layout::Dimension::Remaining) } else if lookahead.peek(syn::Token![?]) { // remove the ',' input.parse::<Punct>()?; - quote!(crate::interface::Dimension::Flexible) + quote!(korangar_interface::layout::Dimension::Flexible) } else if lookahead.peek(syn::Token![super]) { // remove the 'super' input.parse::<syn::Token![super]>()?; - quote!(crate::interface::Dimension::Super) + quote!(korangar_interface::layout::Dimension::Super) } else { let literal: Lit = input.parse()?; @@ -39,9 +39,9 @@ impl syn::parse::Parse for Dimension { if lookahead.peek(syn::Token![%]) { input.parse::<Punct>()?; - quote!(crate::interface::Dimension::Relative(#literal as f32)) + quote!(korangar_interface::layout::Dimension::Relative(#literal as f32)) } else { - quote!(crate::interface::Dimension::Absolute(#literal as f32)) + quote!(korangar_interface::layout::Dimension::Absolute(#literal as f32)) } }; @@ -114,7 +114,7 @@ impl syn::parse::Parse for SizeBound { }; let expanded = quote! { - crate::interface::SizeBound { + korangar_interface::layout::SizeBound { width: #width, minimum_width: #minimum_width, maximum_width: #maximum_width, @@ -161,7 +161,7 @@ impl syn::parse::Parse for DimensionBound { }; let expanded = quote! { - crate::interface::DimensionBound { + korangar_interface::layout::DimensionBound { size: #size, minimum_size: #minimum_size, maximum_size: #maximum_size, diff --git a/procedural/src/lib.rs b/korangar_procedural/src/lib.rs similarity index 100% rename from procedural/src/lib.rs rename to korangar_procedural/src/lib.rs diff --git a/korangar_procedural/src/prototype/element.rs b/korangar_procedural/src/prototype/element.rs new file mode 100644 index 00000000..2c4a5801 --- /dev/null +++ b/korangar_procedural/src/prototype/element.rs @@ -0,0 +1,95 @@ +use proc_macro::TokenStream as InterfaceTokenStream; +use quote::quote; +use syn::{Attribute, DataEnum, DataStruct, Generics, Ident}; + +use super::helper::prototype_element_helper; + +pub fn derive_prototype_element_struct( + data_struct: DataStruct, + generics: Generics, + attributes: Vec<Attribute>, + name: Ident, +) -> InterfaceTokenStream { + let (initializers, is_unnamed, _window_title, _window_class) = prototype_element_helper(data_struct, attributes, name.to_string()); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + if std::env::var("CARGO_PKG_NAME").unwrap() == "korangar" { + if initializers.len() == 1 && is_unnamed { + return quote! { + impl #impl_generics korangar_interface::elements::PrototypeElement<crate::interface::application::InterfaceSettings> for #name #type_generics #where_clause { + fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell<crate::interface::application::InterfaceSettings> { + korangar_interface::elements::PrototypeElement::to_element(&self.0, display) + } + } + } + .into(); + } + + return quote! { + impl #impl_generics korangar_interface::elements::PrototypeElement<crate::interface::application::InterfaceSettings> for #name #type_generics #where_clause { + fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell<crate::interface::application::InterfaceSettings> { + let elements: Vec<korangar_interface::elements::ElementCell<crate::interface::application::InterfaceSettings>> = vec![#(#initializers),*]; + std::rc::Rc::new(std::cell::RefCell::new(korangar_interface::elements::Expandable::new(display, elements, false))) + } + } + } + .into(); + } + + if initializers.len() == 1 && is_unnamed { + return quote! { + impl<App: korangar_interface::settings::Application> #impl_generics korangar_interface::elements::PrototypeElement<App> for #name #type_generics #where_clause { + fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell<App> { + korangar_interface::elements::PrototypeElement::to_element(&self.0, display) + } + } + } + .into(); + } + + quote! { + impl<App: korangar_interface::settings::Application> #impl_generics korangar_interface::elements::PrototypeElement<App> for #name #type_generics #where_clause { + fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell<App> { + let elements: Vec<korangar_interface::elements::ElementCell<App>> = vec![#(#initializers),*]; + std::rc::Rc::new(std::cell::RefCell::new(korangar_interface::elements::Expandable::new(display, elements, false))) + } + } + } + .into() +} + +pub fn derive_prototype_element_enum(data_enum: DataEnum, generics: Generics, name: Ident) -> InterfaceTokenStream { + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + let mut variants = Vec::new(); + let mut variant_strings = Vec::new(); + + for variant in data_enum.variants.into_iter() { + variants.push(variant.ident.clone()); + variant_strings.push(variant.ident.to_string()); + } + + if std::env::var("CARGO_PKG_NAME").unwrap() == "korangar" { + return quote! { + impl #impl_generics korangar_interface::elements::PrototypeElement<crate::interface::application::InterfaceSettings> for #name #type_generics #where_clause { + fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell<crate::interface::application::InterfaceSettings> { + match self { + #( Self::#variants => korangar_interface::elements::PrototypeElement::to_element(&#variant_strings, display), )* + } + } + } + } + .into(); + } + + quote! { + impl<App: korangar_interface::settings::Application> #impl_generics korangar_interface::elements::PrototypeElement<App> for #name #type_generics #where_clause { + fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell<App> { + match self { + #( Self::#variants => korangar_interface::elements::PrototypeElement::to_element(&#variant_strings, display), )* + } + } + } + } + .into() +} diff --git a/procedural/src/prototype/helper.rs b/korangar_procedural/src/prototype/helper.rs similarity index 92% rename from procedural/src/prototype/helper.rs rename to korangar_procedural/src/prototype/helper.rs index b40b13be..85876445 100644 --- a/procedural/src/prototype/helper.rs +++ b/korangar_procedural/src/prototype/helper.rs @@ -43,7 +43,8 @@ pub fn prototype_element_helper( .map(|name: LitStr| name.value()) .unwrap_or_else(|| str::replace(&field_variable.to_string(), "_", " ")); - initializers.push(quote!(crate::interface::PrototypeElement::to_element(&self.#field_identifier, #display_name.to_string()))); + initializers + .push(quote!(korangar_interface::elements::PrototypeElement::to_element(&self.#field_identifier, #display_name.to_string()))); } (initializers, is_unnamed, window_title, window_class) diff --git a/procedural/src/prototype/mod.rs b/korangar_procedural/src/prototype/mod.rs similarity index 100% rename from procedural/src/prototype/mod.rs rename to korangar_procedural/src/prototype/mod.rs diff --git a/korangar_procedural/src/prototype/window.rs b/korangar_procedural/src/prototype/window.rs new file mode 100644 index 00000000..d4d4ff2b --- /dev/null +++ b/korangar_procedural/src/prototype/window.rs @@ -0,0 +1,77 @@ +use proc_macro::TokenStream as InterfaceTokenStream; +use quote::quote; +use syn::{Attribute, DataStruct, Generics, Ident}; + +use super::helper::prototype_element_helper; + +pub fn derive_prototype_window_struct( + data_struct: DataStruct, + generics: Generics, + attributes: Vec<Attribute>, + name: Ident, +) -> InterfaceTokenStream { + let (initializers, _is_unnamed, window_title, window_class) = prototype_element_helper(data_struct, attributes, name.to_string()); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + let (window_class_option, window_class_ref_option) = window_class + .map(|window_class| (quote!(#window_class.to_string().into()), quote!(#window_class.into()))) + .unwrap_or((quote!(None), quote!(None))); + + if std::env::var("CARGO_PKG_NAME").unwrap() == "korangar" { + return quote! { + impl #impl_generics korangar_interface::windows::PrototypeWindow<crate::interface::application::InterfaceSettings> for #name #type_generics #where_clause { + + fn window_class(&self) -> Option<&str> { + #window_class_ref_option + } + + fn to_window(&self, + window_cache: &crate::interface::windows::WindowCache, + application: &crate::interface::application::InterfaceSettings, + available_space: crate::interface::layout::ScreenSize + ) -> korangar_interface::windows::Window<crate::interface::application::InterfaceSettings> { + use crate::interface::application::InterfaceSettings; + use korangar_interface::elements::ElementCell; + use korangar_interface::elements::ScrollView; + use korangar_interface::windows::WindowBuilder; + use korangar_procedural::size_bound; + use std::cell::RefCell; + use std::rc::Rc; + + let scroll_view = ScrollView::new(vec![#(#initializers),*], size_bound!(100%, super > ? < super)); + let elements: Vec<ElementCell<InterfaceSettings>> = vec![Rc::new(RefCell::new(scroll_view))]; + + WindowBuilder::new() + .with_title(#window_title.to_string()) + .with_class_option(#window_class_option) + .with_size_bound(size_bound!(200 > 300 < 400, 0 > ? < 80%)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } + } + }.into(); + } + + quote! { + impl<App: korangar_interface::settings::Application> #impl_generics korangar_interface::windows::PrototypeWindow<App> for #name #type_generics #where_clause { + + fn window_class(&self) -> Option<&str> { + #window_class_ref_option + } + + fn to_window(&self, window_cache: &App::Cache, application: &App, available_space: App::Size) -> korangar_interface::windows::Window<App> { + let scroll_view = korangar_interface::elements::ScrollView::new(vec![#(#initializers),*], korangar_procedural::size_bound!(100%, super > ? < super)); + let elements: Vec<korangar_interface::elements::ElementCell<App>> = vec![std::rc::Rc::new(std::cell::RefCell::new(scroll_view))]; + + korangar_interface::windows::WindowBuilder::new() + .with_title(#window_title.to_string()) + .with_class_option(#window_class_option) + .with_size_bound(korangar_procedural::size_bound!(200 > 300 < 400, 0 > ? < 80%)) + .with_elements(elements) + .closable() + .build(window_cache, application, available_space) + } + } + }.into() +} diff --git a/procedural/src/toggle.rs b/korangar_procedural/src/toggle.rs similarity index 100% rename from procedural/src/toggle.rs rename to korangar_procedural/src/toggle.rs diff --git a/procedural/src/utils.rs b/korangar_procedural/src/utils.rs similarity index 100% rename from procedural/src/utils.rs rename to korangar_procedural/src/utils.rs diff --git a/procedural/src/prototype/element.rs b/procedural/src/prototype/element.rs deleted file mode 100644 index a68a3258..00000000 --- a/procedural/src/prototype/element.rs +++ /dev/null @@ -1,59 +0,0 @@ -use proc_macro::TokenStream as InterfaceTokenStream; -use quote::quote; -use syn::{Attribute, DataEnum, DataStruct, Generics, Ident}; - -use super::helper::prototype_element_helper; - -pub fn derive_prototype_element_struct( - data_struct: DataStruct, - generics: Generics, - attributes: Vec<Attribute>, - name: Ident, -) -> InterfaceTokenStream { - let (initializers, is_unnamed, _window_title, _window_class) = prototype_element_helper(data_struct, attributes, name.to_string()); - let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); - - if initializers.len() == 1 && is_unnamed { - return quote! { - impl #impl_generics crate::interface::PrototypeElement for #name #type_generics #where_clause { - fn to_element(&self, display: String) -> crate::interface::ElementCell { - crate::interface::PrototypeElement::to_element(&self.0, display) - } - } - } - .into(); - } - - quote! { - impl #impl_generics crate::interface::PrototypeElement for #name #type_generics #where_clause { - fn to_element(&self, display: String) -> crate::interface::ElementCell { - let elements: Vec<crate::interface::ElementCell> = vec![#(#initializers),*]; - std::rc::Rc::new(std::cell::RefCell::new(crate::interface::Expandable::new(display, elements, false))) - } - } - } - .into() -} - -pub fn derive_prototype_element_enum(data_enum: DataEnum, generics: Generics, name: Ident) -> InterfaceTokenStream { - let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); - - let mut variants = Vec::new(); - let mut variant_strings = Vec::new(); - - for variant in data_enum.variants.into_iter() { - variants.push(variant.ident.clone()); - variant_strings.push(variant.ident.to_string()); - } - - quote! { - impl #impl_generics crate::interface::PrototypeElement for #name #type_generics #where_clause { - fn to_element(&self, display: String) -> crate::interface::ElementCell { - match self { - #( Self::#variants => crate::interface::PrototypeElement::to_element(&#variant_strings, display), )* - } - } - } - } - .into() -} diff --git a/procedural/src/prototype/window.rs b/procedural/src/prototype/window.rs deleted file mode 100644 index c7453c2b..00000000 --- a/procedural/src/prototype/window.rs +++ /dev/null @@ -1,41 +0,0 @@ -use proc_macro::TokenStream as InterfaceTokenStream; -use quote::quote; -use syn::{Attribute, DataStruct, Generics, Ident}; - -use super::helper::prototype_element_helper; - -pub fn derive_prototype_window_struct( - data_struct: DataStruct, - generics: Generics, - attributes: Vec<Attribute>, - name: Ident, -) -> InterfaceTokenStream { - let (initializers, _is_unnamed, window_title, window_class) = prototype_element_helper(data_struct, attributes, name.to_string()); - let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); - - let (window_class_option, window_class_ref_option) = window_class - .map(|window_class| (quote!(#window_class.to_string().into()), quote!(#window_class.into()))) - .unwrap_or((quote!(None), quote!(None))); - - quote! { - impl #impl_generics crate::interface::PrototypeWindow for #name #type_generics #where_clause { - - fn window_class(&self) -> Option<&str> { - #window_class_ref_option - } - - fn to_window(&self, window_cache: &crate::interface::WindowCache, interface_settings: &crate::interface::InterfaceSettings, available_space: crate::interface::ScreenSize) -> crate::interface::Window { - let scroll_view = crate::interface::ScrollView::new(vec![#(#initializers),*], procedural::size_bound!(100%, super > ? < super)); - let elements: Vec<crate::interface::ElementCell> = vec![std::rc::Rc::new(std::cell::RefCell::new(scroll_view))]; - - crate::interface::WindowBuilder::new() - .with_title(#window_title.to_string()) - .with_class_option(#window_class_option) - .with_size_bound(crate::interface::SizeBound::DEFAULT_FULLY_BOUNDED) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } - } - }.into() -} diff --git a/ragnarok_bytes/Cargo.toml b/ragnarok_bytes/Cargo.toml index a44218f4..7d98b857 100644 --- a/ragnarok_bytes/Cargo.toml +++ b/ragnarok_bytes/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" [dependencies] cgmath = { workspace = true, optional = true } +ragnarok_procedural = { workspace = true, optional = true } [features] cgmath = [ "dep:cgmath" ] +derive = [ "ragnarok_procedural" ] diff --git a/ragnarok_bytes/src/lib.rs b/ragnarok_bytes/src/lib.rs index f8065bb2..6474945a 100644 --- a/ragnarok_bytes/src/lib.rs +++ b/ragnarok_bytes/src/lib.rs @@ -7,6 +7,12 @@ mod helper; mod stream; mod to_bytes; +#[cfg(feature = "derive")] +pub use ragnarok_procedural::{ByteConvertable, FixedByteSize, FromBytes, ToBytes}; +// TODO: Remove this re-export +#[cfg(feature = "derive")] +pub use ragnarok_procedural::{IncomingPacket, OutgoingPacket}; + pub use self::error::{ConversionError, ConversionErrorType}; pub use self::fixed::{FixedByteSize, FixedByteSizeCollection}; pub use self::from_bytes::{FromBytes, FromBytesExt}; diff --git a/ragnarok_procedural/README.md b/ragnarok_procedural/README.md index 959fa455..7c0842ae 100644 --- a/ragnarok_procedural/README.md +++ b/ragnarok_procedural/README.md @@ -3,3 +3,7 @@ A proc macro crate to derive Ragnarok Online traits. All the `ragnarok-*` crates are meant to be independent from Korangar and have no dependencies to it, meaning they can be used for other Ragnarok Online related projects. + +## Note + +This crate is not supposed to be included directly. Use it by activating the `derive` feature of `ragnarok_bytes` or `ragnarok_packets`. diff --git a/src/interface/elements/base.rs b/src/interface/elements/base.rs deleted file mode 100644 index 6e492157..00000000 --- a/src/interface/elements/base.rs +++ /dev/null @@ -1,345 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::rc::{Rc, Weak}; -use std::sync::Arc; - -use cgmath::Vector2; -use vulkano::image::view::ImageView; - -use crate::graphics::{Color, InterfaceRenderer, Renderer, SpriteRenderer}; -use crate::input::MouseInputMode; -use crate::interface::*; -use crate::inventory::{Item, Skill}; - -pub type ElementCell = Rc<RefCell<dyn Element>>; -pub type WeakElementCell = Weak<RefCell<dyn Element>>; - -pub trait ElementWrap { - fn wrap(self) -> ElementCell; -} - -impl<T> ElementWrap for T -where - T: Element + Sized + 'static, -{ - fn wrap(self) -> ElementCell { - Rc::new(RefCell::new(self)) - } -} - -pub struct ElementRenderer<'a> { - pub render_target: &'a mut <InterfaceRenderer as Renderer>::Target, - pub renderer: &'a InterfaceRenderer, - pub interface_settings: &'a InterfaceSettings, - pub position: ScreenPosition, - pub size: ScreenSize, - pub screen_clip: ScreenClip, -} - -impl<'a> ElementRenderer<'a> { - pub fn get_position(&self) -> ScreenPosition { - self.position - } - - pub fn get_text_dimensions(&self, text: &str, font_size: f32, available_width: f32) -> Vector2<f32> { - self.renderer - .get_text_dimensions(text, font_size * self.interface_settings.scaling.get(), available_width) - } - - pub fn set_scroll(&mut self, scroll: f32) { - self.position.top -= scroll; - } - - pub fn render_background(&mut self, corner_radius: CornerRadius, color: Color) { - self.renderer.render_rectangle( - self.render_target, - self.position, - self.size, - self.screen_clip, - corner_radius * self.interface_settings.scaling.get(), - color, - ); - } - - pub fn render_rectangle(&mut self, position: ScreenPosition, size: ScreenSize, corner_radius: CornerRadius, color: Color) { - self.renderer.render_rectangle( - self.render_target, - self.position + position, - size, - self.screen_clip, - corner_radius * self.interface_settings.scaling.get(), - color, - ); - } - - pub fn render_text(&mut self, text: &str, offset: ScreenPosition, foreground_color: Color, font_size: f32) -> f32 { - self.renderer.render_text( - self.render_target, - text, - self.position + offset * self.interface_settings.scaling.get(), - self.screen_clip, - foreground_color, - font_size * self.interface_settings.scaling.get(), - ) - } - - pub fn render_checkbox(&mut self, offset: ScreenPosition, size: ScreenSize, color: Color, checked: bool) { - self.renderer.render_checkbox( - self.render_target, - self.position + offset * self.interface_settings.scaling.get(), - size * self.interface_settings.scaling.get(), - self.screen_clip, - color, - checked, - ); - } - - pub fn render_expand_arrow(&mut self, offset: ScreenPosition, size: ScreenSize, color: Color, expanded: bool) { - self.renderer.render_expand_arrow( - self.render_target, - self.position + offset * self.interface_settings.scaling.get(), - size * self.interface_settings.scaling.get(), - self.screen_clip, - color, - expanded, - ); - } - - pub fn render_sprite(&mut self, texture: Arc<ImageView>, offset: ScreenPosition, size: ScreenSize, color: Color) { - self.renderer.render_sprite( - self.render_target, - texture, - self.position + offset * self.interface_settings.scaling.get(), - size * self.interface_settings.scaling.get(), - self.screen_clip, - color, - false, - ); - } - - pub fn render_element( - &mut self, - element: &dyn Element, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - second_theme: bool, - ) { - element.render( - self.render_target, - self.renderer, - state_provider, - interface_settings, - theme, - self.position, - self.screen_clip, - hovered_element, - focused_element, - mouse_mode, - second_theme, - ) - } -} - -#[derive(Default)] -pub struct ElementState { - pub cached_size: ScreenSize, - pub cached_position: ScreenPosition, - pub self_element: Option<WeakElementCell>, - pub parent_element: Option<WeakElementCell>, - pub mouse_position: Cell<ScreenPosition>, -} - -impl ElementState { - pub fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option<WeakElementCell>) { - self.self_element = Some(weak_self); - self.parent_element = weak_parent; - } - - pub fn resolve(&mut self, placement_resolver: &mut PlacementResolver, size_bound: &SizeBound) { - let (size, position) = placement_resolver.allocate(size_bound); - self.cached_size = size.finalize(); - self.cached_position = position; - } - - pub fn hovered_element(&self, mouse_position: ScreenPosition) -> HoverInformation { - let absolute_position = ScreenPosition::from_size(mouse_position - self.cached_position); - - if absolute_position.left >= 0.0 - && absolute_position.top >= 0.0 - && absolute_position.left <= self.cached_size.width - && absolute_position.top <= self.cached_size.height - { - self.mouse_position.replace(absolute_position); - return HoverInformation::Hovered; - } - - HoverInformation::Missed - } - - pub fn element_renderer<'a>( - &self, - render_target: &'a mut <InterfaceRenderer as Renderer>::Target, - renderer: &'a InterfaceRenderer, - interface_settings: &'a InterfaceSettings, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - ) -> ElementRenderer<'a> { - let position = parent_position + self.cached_position; - let size = self.cached_size; - - let screen_clip = ScreenClip { - left: screen_clip.left.max(position.left), - top: screen_clip.top.max(position.top), - right: screen_clip.right.min(position.left + self.cached_size.width), - bottom: screen_clip.bottom.min(position.top + self.cached_size.height), - }; - - ElementRenderer { - render_target, - renderer, - interface_settings, - position, - size, - screen_clip, - } - } -} - -#[derive(Clone, Copy, new)] -pub struct Focus { - pub mode: FocusMode, - #[new(default)] - pub downwards: bool, -} - -impl Focus { - pub fn downwards() -> Self { - Self { - mode: FocusMode::FocusNext, - downwards: true, - } - } - - pub fn to_downwards(self) -> Self { - Focus { - mode: self.mode, - downwards: true, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum FocusMode { - FocusNext, - FocusPrevious, -} - -impl From<bool> for FocusMode { - fn from(reverse: bool) -> Self { - match reverse { - true => Self::FocusPrevious, - false => Self::FocusNext, - } - } -} - -pub trait Element { - fn get_state(&self) -> &ElementState; - - fn get_state_mut(&mut self) -> &mut ElementState; - - fn link_back(&mut self, weak_self: WeakElementCell, weak_parent: Option<WeakElementCell>) { - self.get_state_mut().link_back(weak_self, weak_parent); - } - - fn is_focusable(&self) -> bool { - true - } - - fn focus_next( - &self, - self_cell: Rc<RefCell<dyn Element>>, - _caller_cell: Option<Rc<RefCell<dyn Element>>>, - focus: Focus, - ) -> Option<Rc<RefCell<dyn Element>>> { - if focus.downwards { - return Some(self_cell); - } - - self.get_state().parent_element.as_ref().and_then(|parent_element| { - let parent_element = parent_element.upgrade().unwrap(); - let next_element = parent_element.borrow().focus_next(parent_element.clone(), Some(self_cell), focus); - next_element - }) - } - - fn restore_focus(&self, self_cell: ElementCell) -> Option<ElementCell> { - self.is_focusable().then_some(self_cell) - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme); - - fn update(&mut self) -> Option<ChangeEvent> { - None - } - - fn is_element_self(&self, element: Option<&dyn Element>) -> bool { - matches!(element, Some(reference) if std::ptr::eq(reference as *const _ as *const (), self as *const _ as *const ())) - } - - fn hovered_element(&self, _mouse_position: ScreenPosition, _mouse_mode: &MouseInputMode) -> HoverInformation { - HoverInformation::Missed - } - - fn left_click(&mut self, _update: &mut bool) -> Vec<ClickAction> { - Vec::new() - } - - fn right_click(&mut self, _update: &mut bool) -> Vec<ClickAction> { - Vec::new() - } - - fn drag(&mut self, _mouse_delta: ScreenPosition) -> Option<ChangeEvent> { - None - } - - fn input_character(&mut self, _character: char) -> Vec<ClickAction> { - Vec::new() - } - - fn drop_item(&mut self, _item_source: ItemSource, _item: Item) -> Option<ItemMove> { - None - } - - fn drop_skill(&mut self, skill_source: SkillSource, skill: Skill) -> Option<SkillMove> { - let _ = skill_source; - let _ = skill; - None - } - - fn scroll(&mut self, delta: f32) -> Option<ChangeEvent> { - self.get_state() - .parent_element - .as_ref() - .and_then(|weak_pointer| weak_pointer.upgrade()) - .and_then(|element| (*element).borrow_mut().scroll(delta)) - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - render: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - second_theme: bool, - ); -} diff --git a/src/interface/elements/buttons/close/mod.rs b/src/interface/elements/buttons/close/mod.rs deleted file mode 100644 index 046cc2e9..00000000 --- a/src/interface/elements/buttons/close/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -mod builder; - -pub use self::builder::CloseButtonBuilder; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::*; - -pub struct CloseButton { - state: ElementState, -} - -impl Element for CloseButton { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let (size, position) = placement_resolver.allocate_right(&theme.close_button.size_bound); - self.state.cached_size = size.finalize(); - self.state.cached_position = position; - } - - fn is_focusable(&self) -> bool { - false - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> { - vec![ClickAction::CloseWindow] - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { - true => theme.close_button.hovered_background_color.get(), - false => theme.close_button.background_color.get(), - }; - - renderer.render_background(theme.close_button.corner_radius.get(), background_color); - - renderer.render_text( - "X", - theme.close_button.text_offset.get(), - theme.close_button.foreground_color.get(), - theme.close_button.font_size.get(), - ); - } -} diff --git a/src/interface/elements/buttons/default/builder.rs b/src/interface/elements/buttons/default/builder.rs deleted file mode 100644 index 1d354099..00000000 --- a/src/interface/elements/buttons/default/builder.rs +++ /dev/null @@ -1,130 +0,0 @@ -use procedural::dimension_bound; - -use super::Button; -use crate::interface::builder::{Set, Unset}; -use crate::interface::*; - -/// Type state [`Button`] builder. This builder utilizes the type system to -/// prevent calling the same method multiple times and calling -/// [`build`](Self::build) before the mandatory methods have been called. -#[must_use = "`build` needs to be called"] -pub struct ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> { - text: TEXT, - event: EVENT, - disabled_selector: Option<Selector>, - foreground_color: Option<ColorSelector>, - background_color: Option<ColorSelector>, - width_bound: DimensionBound, - marker: PhantomData<(DISABLED, FOREGROUND, BACKGROUND, WIDTH)>, -} - -impl ButtonBuilder<Unset, Unset, Unset, Unset, Unset, Unset> { - pub fn new() -> Self { - Self { - text: Unset, - event: Unset, - disabled_selector: None, - foreground_color: None, - background_color: None, - width_bound: dimension_bound!(100%), - marker: PhantomData, - } - } -} - -impl<EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> ButtonBuilder<Unset, EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> { - pub fn with_text<TEXT: AsRef<str> + 'static>(self, text: TEXT) -> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> { - ButtonBuilder { text, ..self } - } -} - -impl<TEXT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> ButtonBuilder<TEXT, Unset, DISABLED, FOREGROUND, BACKGROUND, WIDTH> { - pub fn with_event<EVENT: ElementEvent + 'static>( - self, - event: EVENT, - ) -> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> { - ButtonBuilder { event, ..self } - } -} - -impl<TEXT, EVENT, FOREGROUND, BACKGROUND, WIDTH> ButtonBuilder<TEXT, EVENT, Unset, FOREGROUND, BACKGROUND, WIDTH> { - pub fn with_disabled_selector( - self, - selector: impl Fn() -> bool + 'static, - ) -> ButtonBuilder<TEXT, EVENT, Set, FOREGROUND, BACKGROUND, WIDTH> { - ButtonBuilder { - disabled_selector: Some(Box::new(selector)), - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, DISABLED, BACKGROUND, WIDTH> ButtonBuilder<TEXT, EVENT, DISABLED, Unset, BACKGROUND, WIDTH> { - pub fn with_foreground_color( - self, - color_selector: impl Fn(&InterfaceTheme) -> Color + 'static, - ) -> ButtonBuilder<TEXT, EVENT, DISABLED, Set, BACKGROUND, WIDTH> { - ButtonBuilder { - foreground_color: Some(Box::new(color_selector)), - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, DISABLED, FOREGROUND, WIDTH> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, Unset, WIDTH> { - pub fn with_background_color( - self, - color_selector: impl Fn(&InterfaceTheme) -> Color + 'static, - ) -> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, Set, WIDTH> { - ButtonBuilder { - background_color: Some(Box::new(color_selector)), - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, Unset> { - pub fn with_width_bound(self, width_bound: DimensionBound) -> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, Set> { - ButtonBuilder { - width_bound, - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> ButtonBuilder<TEXT, EVENT, DISABLED, FOREGROUND, BACKGROUND, WIDTH> -where - TEXT: AsRef<str> + 'static, - EVENT: ElementEvent + 'static, -{ - /// Take the builder and turn it into a [`Button`]. - /// - /// NOTE: This method is only available if [`with_text`](Self::with_text) - /// and [`with_event`](Self::with_event) have been called on - /// the builder. - pub fn build(self) -> Button<TEXT, EVENT> { - let Self { - text, - event, - disabled_selector, - foreground_color, - background_color, - width_bound, - .. - } = self; - - Button { - text, - event, - disabled_selector, - foreground_color, - background_color, - width_bound, - state: Default::default(), - } - } -} diff --git a/src/interface/elements/buttons/default/mod.rs b/src/interface/elements/buttons/default/mod.rs deleted file mode 100644 index 7eaf848a..00000000 --- a/src/interface/elements/buttons/default/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -mod builder; - -pub use self::builder::ButtonBuilder; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::*; - -pub struct Button<TEXT, EVENT> -where - TEXT: AsRef<str> + 'static, - EVENT: ElementEvent + 'static, -{ - text: TEXT, - event: EVENT, - disabled_selector: Option<Selector>, - foreground_color: Option<ColorSelector>, - background_color: Option<ColorSelector>, - width_bound: DimensionBound, - state: ElementState, -} - -impl<TEXT, EVENT> Button<TEXT, EVENT> -where - TEXT: AsRef<str> + 'static, - EVENT: ElementEvent + 'static, -{ - fn is_disabled(&self) -> bool { - self.disabled_selector.as_ref().map(|selector| !selector()).unwrap_or(false) - } -} - -impl<TEXT: AsRef<str> + 'static, EVENT: ElementEvent> Element for Button<TEXT, EVENT> { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn is_focusable(&self) -> bool { - !self.is_disabled() - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let size_bound = self.width_bound.add_height(theme.button.height_bound); - self.state.resolve(placement_resolver, &size_bound); - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> { - match self.is_disabled() { - true => Vec::new(), - false => self.event.trigger(), - } - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let disabled = self.is_disabled(); - let background_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { - _ if disabled => theme.button.disabled_background_color.get(), - true => theme.button.hovered_background_color.get(), - false if self.background_color.is_some() => (self.background_color.as_ref().unwrap())(theme), - false => theme.button.background_color.get(), - }; - - renderer.render_background(theme.button.corner_radius.get(), background_color); - - let foreground_color = if disabled { - theme.button.disabled_foreground_color.get() - } else { - self.foreground_color - .as_ref() - .map(|closure| closure(theme)) - .unwrap_or(theme.button.foreground_color.get()) - }; - - renderer.render_text( - self.text.as_ref(), - theme.button.text_offset.get(), - foreground_color, - theme.button.font_size.get(), - ); - } -} diff --git a/src/interface/elements/buttons/drag/mod.rs b/src/interface/elements/buttons/drag/mod.rs deleted file mode 100644 index 3dc96ab1..00000000 --- a/src/interface/elements/buttons/drag/mod.rs +++ /dev/null @@ -1,77 +0,0 @@ -mod builder; - -pub use self::builder::DragButtonBuilder; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::*; - -pub struct DragButton { - title: String, - width_bound: DimensionBound, - state: ElementState, -} - -impl Element for DragButton { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let size_bound = self.width_bound.add_height(theme.window.title_height); - - self.state.resolve(placement_resolver, &size_bound); - } - - fn is_focusable(&self) -> bool { - false - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - MouseInputMode::DragElement((element, _)) if self.is_element_self(Some(&*element.borrow())) => HoverInformation::Hovered, - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> { - vec![ClickAction::MoveInterface] - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - if self.is_element_self(hovered_element) { - renderer.render_background( - theme.window.title_corner_radius.get(), - theme.window.title_background_color.get(), - ); - } - - renderer.render_text( - &self.title, - theme.window.text_offset.get(), - theme.window.foreground_color.get(), - theme.window.font_size.get(), - ); - } -} diff --git a/src/interface/elements/buttons/state/builder.rs b/src/interface/elements/buttons/state/builder.rs deleted file mode 100644 index 0747aa83..00000000 --- a/src/interface/elements/buttons/state/builder.rs +++ /dev/null @@ -1,108 +0,0 @@ -use procedural::dimension_bound; - -use super::{StateButton, StateSelector}; -use crate::interface::builder::{Set, Unset}; -use crate::interface::*; - -/// Type state [`StateButton`] builder. This builder utilizes the type system to -/// prevent calling the same method multiple times and calling -/// [`build`](Self::build) before the mandatory methods have been called. -#[must_use = "`build` needs to be called"] -pub struct StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, WIDTH> { - text: TEXT, - event: EVENT, - selector: SELECTOR, - transparent_background: bool, - width_bound: DimensionBound, - marker: PhantomData<(SELECTOR, BACKGROUND, WIDTH)>, -} - -impl StateButtonBuilder<Unset, Unset, Unset, Unset, Unset> { - pub fn new() -> Self { - Self { - text: Unset, - event: Unset, - selector: Unset, - transparent_background: false, - width_bound: dimension_bound!(100%), - marker: PhantomData, - } - } -} - -impl<EVENT, SELECTOR, BACKGROUND, WIDTH> StateButtonBuilder<Unset, EVENT, SELECTOR, BACKGROUND, WIDTH> { - pub fn with_text<TEXT: AsRef<str> + 'static>(self, text: TEXT) -> StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, WIDTH> { - StateButtonBuilder { text, ..self } - } -} - -impl<TEXT, SELECTOR, BACKGROUND, WIDTH> StateButtonBuilder<TEXT, Unset, SELECTOR, BACKGROUND, WIDTH> { - pub fn with_event<EVENT: ElementEvent + 'static>(self, event: EVENT) -> StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, WIDTH> { - StateButtonBuilder { event, ..self } - } -} - -impl<TEXT, EVENT, BACKGROUND, WIDTH> StateButtonBuilder<TEXT, EVENT, Unset, BACKGROUND, WIDTH> { - pub fn with_selector( - self, - selector: impl Fn(&StateProvider) -> bool + 'static, - ) -> StateButtonBuilder<TEXT, EVENT, StateSelector, BACKGROUND, WIDTH> { - StateButtonBuilder { - selector: Box::new(selector), - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, SELECTOR, WIDTH> StateButtonBuilder<TEXT, EVENT, SELECTOR, Unset, WIDTH> { - pub fn with_transparent_background(self) -> StateButtonBuilder<TEXT, EVENT, SELECTOR, Set, WIDTH> { - StateButtonBuilder { - transparent_background: true, - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, SELECTOR, BACKGROUND> StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, Unset> { - pub fn with_width_bound(self, width_bound: DimensionBound) -> StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, Set> { - StateButtonBuilder { - width_bound, - marker: PhantomData, - ..self - } - } -} - -impl<TEXT, EVENT, BACKGROUND, WIDTH> StateButtonBuilder<TEXT, EVENT, StateSelector, BACKGROUND, WIDTH> -where - TEXT: AsRef<str> + 'static, - EVENT: ElementEvent + 'static, -{ - /// Take the builder and turn it into a [`StateButton`]. - /// - /// NOTE: This method is only available if [`with_text`](Self::with_text), - /// [`with_event`](Self::with_event), and - /// [`with_selector`](Self::with_selector) have been called on - /// the builder. - pub fn build(self) -> StateButton<TEXT, EVENT> { - let Self { - text, - event, - selector, - transparent_background, - width_bound, - .. - } = self; - - StateButton { - text, - event, - selector, - transparent_background, - width_bound, - state: Default::default(), - } - } -} diff --git a/src/interface/elements/buttons/state/mod.rs b/src/interface/elements/buttons/state/mod.rs deleted file mode 100644 index 7b20c5fa..00000000 --- a/src/interface/elements/buttons/state/mod.rs +++ /dev/null @@ -1,101 +0,0 @@ -mod builder; - -pub use self::builder::StateButtonBuilder; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::{Element, *}; - -type StateSelector = Box<dyn Fn(&StateProvider) -> bool + 'static>; - -// FIX: State button won't redraw just because the state changes -pub struct StateButton<TEXT, EVENT> -where - TEXT: AsRef<str> + 'static, - EVENT: ElementEvent + 'static, -{ - text: TEXT, - event: EVENT, - selector: StateSelector, - width_bound: DimensionBound, - transparent_background: bool, - state: ElementState, -} - -impl<TEXT, EVENT> Element for StateButton<TEXT, EVENT> -where - TEXT: AsRef<str> + 'static, - EVENT: ElementEvent + 'static, -{ - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let size_bound = self.width_bound.add_height(theme.button.height_bound); - self.state.resolve(placement_resolver, &size_bound); - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> { - self.event.trigger() - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let highlighted = self.is_element_self(hovered_element) || self.is_element_self(focused_element); - - if !self.transparent_background { - let background_color = match highlighted { - true => theme.button.hovered_background_color.get(), - false => theme.button.background_color.get(), - }; - - renderer.render_background(theme.button.corner_radius.get(), background_color); - } - - let foreground_color = match self.transparent_background && highlighted { - true => theme.button.hovered_foreground_color.get(), - false => theme.button.foreground_color.get(), - }; - - renderer.render_checkbox( - theme.button.icon_offset.get(), - theme.button.icon_size.get(), - foreground_color, - (self.selector)(state_provider), - ); - - renderer.render_text( - self.text.as_ref(), - theme.button.icon_text_offset.get(), - foreground_color, - theme.button.font_size.get(), - ); - } -} diff --git a/src/interface/elements/containers/default.rs b/src/interface/elements/containers/default.rs deleted file mode 100644 index f37e5826..00000000 --- a/src/interface/elements/containers/default.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::rc::Weak; - -use procedural::size_bound; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::{Element, *}; - -pub struct Container { - size_bound: Option<SizeBound>, - border_size: Option<ScreenSize>, - state: ContainerState, -} - -impl Container { - pub fn new(elements: Vec<ElementCell>) -> Self { - Self { - state: ContainerState::new(elements), - border_size: None, - size_bound: None, - } - } - - pub fn with_size(mut self, size_bound: SizeBound) -> Self { - self.size_bound = Some(size_bound); - self - } -} - -impl Element for Container { - fn get_state(&self) -> &ElementState { - &self.state.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state.state - } - - fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element>>, weak_parent: Option<Weak<RefCell<dyn Element>>>) { - self.state.link_back(weak_self, weak_parent); - } - - fn is_focusable(&self) -> bool { - self.state.is_focusable::<false>() - } - - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option<ElementCell>, focus: Focus) -> Option<ElementCell> { - self.state.focus_next::<false>(self_cell, caller_cell, focus) - } - - fn restore_focus(&self, self_cell: ElementCell) -> Option<ElementCell> { - self.state.restore_focus(self_cell) - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let size_bound = self.size_bound.as_ref().unwrap_or(&size_bound!(100%, ?)); - let border = self.border_size.unwrap_or_default(); - - self.state - .resolve(placement_resolver, interface_settings, theme, size_bound, border); - } - - fn update(&mut self) -> Option<ChangeEvent> { - self.state.update() - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - self.state.hovered_element(mouse_position, mouse_mode, false) - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - second_theme: bool, - ) { - let mut renderer = self - .state - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - self.state.render( - &mut renderer, - state_provider, - interface_settings, - theme, - hovered_element, - focused_element, - mouse_mode, - second_theme, - ); - } -} diff --git a/src/interface/elements/containers/expandable.rs b/src/interface/elements/containers/expandable.rs deleted file mode 100644 index 825a2cbb..00000000 --- a/src/interface/elements/containers/expandable.rs +++ /dev/null @@ -1,213 +0,0 @@ -use std::rc::Weak; - -use procedural::size_bound; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::{Element, *}; - -pub struct Expandable { - display: String, - expanded: bool, - open_size_bound: SizeBound, - closed_size_bound: SizeBound, - cached_closed_size: ScreenSize, - state: ContainerState, -} - -impl Expandable { - pub fn new(display: String, elements: Vec<ElementCell>, expanded: bool) -> Self { - let state = ContainerState::new(elements); - - Self { - display, - expanded, - open_size_bound: size_bound!(100%, ?), - closed_size_bound: size_bound!(100%, 18), - cached_closed_size: ScreenSize::default(), - state, - } - } -} - -impl Element for Expandable { - fn get_state(&self) -> &ElementState { - &self.state.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state.state - } - - fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element>>, weak_parent: Option<Weak<RefCell<dyn Element>>>) { - self.state.link_back(weak_self, weak_parent); - } - - fn is_focusable(&self) -> bool { - self.state.is_focusable::<true>() - } - - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option<ElementCell>, focus: Focus) -> Option<ElementCell> { - // TODO: fix collapsed elements being focusable - self.state.focus_next::<true>(self_cell, caller_cell, focus) - } - - fn restore_focus(&self, self_cell: ElementCell) -> Option<ElementCell> { - self.state.restore_focus(self_cell) - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let closed_size = self - .closed_size_bound - .resolve_element( - placement_resolver.get_available(), - placement_resolver.get_remaining(), - &placement_resolver.get_parent_limits(), - interface_settings.scaling.get(), - ) - .finalize(); - - let size_bound = match self.expanded && !self.state.elements.is_empty() { - true => &self.open_size_bound, - false => &self.closed_size_bound, - }; - - let screen_position = - ScreenPosition::only_top(closed_size.height) + theme.expandable.element_offset.get() * interface_settings.scaling.get(); - - let (mut inner_placement_resolver, mut size, position) = - placement_resolver.derive(size_bound, screen_position, theme.expandable.border_size.get()); - let parent_limits = inner_placement_resolver.get_parent_limits(); - - if self.expanded && !self.state.elements.is_empty() { - inner_placement_resolver.set_gaps(theme.expandable.gaps.get()); - - self.state.elements.iter_mut().for_each(|element| { - element - .borrow_mut() - .resolve(&mut inner_placement_resolver, interface_settings, theme) - }); - - if self.open_size_bound.height.is_flexible() { - let final_height = inner_placement_resolver.final_height() - + closed_size.height - + theme.expandable.element_offset.get().top * interface_settings.scaling.get() - + theme.expandable.border_size.get().height * interface_settings.scaling.get() * 2.0; - - let final_height = self.open_size_bound.validated_height( - final_height, - placement_resolver.get_available().height, - placement_resolver.get_available().height, - &parent_limits, - interface_settings.scaling.get(), - ); - - size.height = Some(final_height); - placement_resolver.register_height(final_height); - } - } - - self.cached_closed_size = closed_size; - self.state.state.cached_size = size.finalize(); - self.state.state.cached_position = position; - } - - fn update(&mut self) -> Option<ChangeEvent> { - if !self.expanded || self.state.elements.is_empty() { - return None; - } - - self.state.update() - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - let absolute_position = ScreenPosition::from_size(mouse_position - self.state.state.cached_position); - - if absolute_position.left >= 0.0 - && absolute_position.top >= 0.0 - && absolute_position.left <= self.state.state.cached_size.width - && absolute_position.top <= self.state.state.cached_size.height - { - if self.expanded && !self.state.elements.is_empty() { - for element in &self.state.elements { - match element.borrow().hovered_element(absolute_position, mouse_mode) { - HoverInformation::Hovered => return HoverInformation::Element(element.clone()), - HoverInformation::Missed => {} - hover_information => return hover_information, - } - } - } - - if mouse_mode.is_none() { - return HoverInformation::Hovered; - } - } - - HoverInformation::Missed - } - - fn left_click(&mut self, force_update: &mut bool) -> Vec<ClickAction> { - self.expanded = !self.expanded; - *force_update = true; - Vec::new() - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - second_theme: bool, - ) { - let mut renderer = self - .state - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let background_color = match second_theme { - true => theme.expandable.second_background_color.get(), - false => theme.expandable.background_color.get(), - }; - - renderer.render_background(theme.button.corner_radius.get(), background_color); - - renderer.render_expand_arrow( - theme.expandable.icon_offset.get(), - theme.expandable.icon_size.get(), - theme.expandable.foreground_color.get(), - self.expanded, - ); - - let foreground_color = match self.is_element_self(hovered_element) || self.is_element_self(focused_element) { - true => theme.expandable.hovered_foreground_color.get(), - false => theme.expandable.foreground_color.get(), - }; - - renderer.render_text( - &self.display, - theme.expandable.text_offset.get(), - foreground_color, - theme.expandable.font_size.get(), - ); - - if self.expanded && !self.state.elements.is_empty() { - self.state.render( - &mut renderer, - state_provider, - interface_settings, - theme, - hovered_element, - focused_element, - mouse_mode, - !second_theme, - ); - } - } -} diff --git a/src/interface/elements/containers/friends.rs b/src/interface/elements/containers/friends.rs deleted file mode 100644 index c65a3591..00000000 --- a/src/interface/elements/containers/friends.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::cell::UnsafeCell; -use std::rc::Weak; - -use procedural::size_bound; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::*; -use crate::network::Friend; - -pub struct FriendView { - friends: Remote<Vec<(Friend, UnsafeCell<Option<WeakElementCell>>)>>, - state: ContainerState, -} - -impl FriendView { - pub fn new(friends: Remote<Vec<(Friend, UnsafeCell<Option<WeakElementCell>>)>>) -> Self { - let elements = { - let friends = friends.borrow(); - - friends - .iter() - .map(|(friend, linked_element)| { - let element = Self::friend_to_element(friend); - unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; - element - }) - .collect() - }; - - Self { - friends, - state: ContainerState::new(elements), - } - } - - fn friend_to_element(friend: &Friend) -> ElementCell { - let elements = vec![ - ButtonBuilder::new() - .with_text("remove") - .with_event(UserEvent::RemoveFriend { - account_id: friend.account_id, - character_id: friend.character_id, - }) - .build() - .wrap(), - ]; - - Expandable::new(friend.name.clone(), elements, false).wrap() - } -} - -impl Element for FriendView { - fn get_state(&self) -> &ElementState { - &self.state.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state.state - } - - fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element>>, weak_parent: Option<Weak<RefCell<dyn Element>>>) { - self.state.link_back(weak_self, weak_parent); - } - - fn is_focusable(&self) -> bool { - self.state.is_focusable::<false>() - } - - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option<ElementCell>, focus: Focus) -> Option<ElementCell> { - self.state.focus_next::<false>(self_cell, caller_cell, focus) - } - - fn restore_focus(&self, self_cell: ElementCell) -> Option<ElementCell> { - self.state.restore_focus(self_cell) - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - self.state.resolve( - placement_resolver, - interface_settings, - theme, - &size_bound!(100%, ?), - ScreenSize::default(), - ); - } - - fn update(&mut self) -> Option<ChangeEvent> { - let mut resolve = false; - - if self.friends.consume_changed() { - // Remove elements of old friends from the start of the list and add new friends - // to the list. - self.friends - .borrow() - .iter() - .enumerate() - .for_each(|(index, (friend, linked_element))| { - if let Some(linked_element) = unsafe { &(*linked_element.get()) } { - while !std::ptr::addr_eq(linked_element.as_ptr(), Rc::downgrade(&self.state.elements[index]).as_ptr()) { - self.state.elements.remove(index); - } - } else { - let element = Self::friend_to_element(friend); - unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; - let weak_self = self.state.state.self_element.clone(); - - element.borrow_mut().link_back(Rc::downgrade(&element), weak_self); - - self.state.elements.insert(index, element); - resolve = true; - } - }); - - // Remove elements of old friends from the end of the list. - let friend_count = self.friends.borrow().len(); - if friend_count < self.state.elements.len() { - self.state.elements.truncate(friend_count); - resolve = true; - } - } - - match resolve { - true => Some(ChangeEvent::RESOLVE_WINDOW), - false => None, - } - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position, mouse_mode, false), - _ => HoverInformation::Missed, - } - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - second_theme: bool, - ) { - let mut renderer = self - .state - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - self.state.render( - &mut renderer, - state_provider, - interface_settings, - theme, - hovered_element, - focused_element, - mouse_mode, - second_theme, - ); - } -} diff --git a/src/interface/elements/containers/scroll.rs b/src/interface/elements/containers/scroll.rs deleted file mode 100644 index 93011cd1..00000000 --- a/src/interface/elements/containers/scroll.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::rc::Weak; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::{Element, *}; - -const SCROLL_SPEED: f32 = 0.8; - -pub struct ScrollView { - scroll: f32, - children_height: f32, - state: ContainerState, - size_bound: SizeBound, - background_color: Option<ColorSelector>, -} - -impl ScrollView { - pub fn new(elements: Vec<ElementCell>, size_bound: SizeBound) -> Self { - let scroll = 0.0; - let children_height = 0.0; - let state = ContainerState::new(elements); - let background_color = None; - - Self { - scroll, - children_height, - state, - size_bound, - background_color, - } - } - - pub fn with_background_color(mut self, background_color: impl Fn(&InterfaceTheme) -> Color + 'static) -> Self { - self.background_color = Some(Box::new(background_color)); - self - } - - fn clamp_scroll(&mut self) { - self.scroll = self - .scroll - .clamp(0.0, (self.children_height - self.state.state.cached_size.height).max(0.0)); - } -} - -impl Element for ScrollView { - fn get_state(&self) -> &ElementState { - &self.state.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state.state - } - - fn link_back(&mut self, weak_self: Weak<RefCell<dyn Element>>, weak_parent: Option<Weak<RefCell<dyn Element>>>) { - self.state.link_back(weak_self, weak_parent); - } - - fn is_focusable(&self) -> bool { - self.state.is_focusable::<false>() - } - - fn focus_next(&self, self_cell: ElementCell, caller_cell: Option<ElementCell>, focus: Focus) -> Option<ElementCell> { - self.state.focus_next::<false>(self_cell, caller_cell, focus) - } - - fn restore_focus(&self, self_cell: ElementCell) -> Option<ElementCell> { - self.state.restore_focus(self_cell) - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - self.children_height = self.state.resolve( - placement_resolver, - interface_settings, - theme, - &self.size_bound, - ScreenSize::default(), - ); - self.clamp_scroll(); - } - - fn update(&mut self) -> Option<ChangeEvent> { - self.state.update() - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - let absolute_position = ScreenPosition::from_size(mouse_position - self.state.state.cached_position); - - if absolute_position.left >= 0.0 - && absolute_position.top >= 0.0 - && absolute_position.left <= self.state.state.cached_size.width - && absolute_position.top <= self.state.state.cached_size.height - { - for element in &self.state.elements { - match element - .borrow() - .hovered_element(absolute_position + ScreenPosition::only_top(self.scroll), mouse_mode) - { - HoverInformation::Hovered => return HoverInformation::Element(element.clone()), - HoverInformation::Missed => {} - hover_information => return hover_information, - } - } - - if mouse_mode.is_none() { - return HoverInformation::Hovered; - } - } - - HoverInformation::Missed - } - - fn scroll(&mut self, delta: f32) -> Option<ChangeEvent> { - self.scroll -= delta * SCROLL_SPEED; - self.clamp_scroll(); - Some(ChangeEvent::RENDER_WINDOW) - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - second_theme: bool, - ) { - let mut renderer = self - .state - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - if let Some(color_selector) = &self.background_color { - renderer.render_background(theme.button.corner_radius.get(), color_selector(theme)); - } - - renderer.set_scroll(self.scroll); - - self.state.render( - &mut renderer, - state_provider, - interface_settings, - theme, - hovered_element, - focused_element, - mouse_mode, - second_theme, - ); - } -} diff --git a/src/interface/elements/miscellanious/headline.rs b/src/interface/elements/miscellanious/headline.rs deleted file mode 100644 index 7db4e8fe..00000000 --- a/src/interface/elements/miscellanious/headline.rs +++ /dev/null @@ -1,61 +0,0 @@ -use derive_new::new; -use procedural::size_bound; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::interface::{Element, *}; - -#[derive(new)] -pub struct Headline { - display: String, - size_bound: SizeBound, - #[new(default)] - state: ElementState, -} - -impl Headline { - pub const DEFAULT_SIZE: SizeBound = size_bound!(100%, 12); -} - -impl Element for Headline { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn is_focusable(&self) -> bool { - false - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, _theme: &InterfaceTheme) { - self.state.resolve(placement_resolver, &self.size_bound); - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - renderer.render_text( - &self.display, - theme.label.text_offset.get(), - theme.label.foreground_color.get(), - theme.label.font_size.get(), - ); - } -} diff --git a/src/interface/elements/miscellanious/input/mod.rs b/src/interface/elements/miscellanious/input/mod.rs deleted file mode 100644 index 793a7350..00000000 --- a/src/interface/elements/miscellanious/input/mod.rs +++ /dev/null @@ -1,151 +0,0 @@ -mod builder; - -use std::fmt::Display; - -pub use self::builder::InputFieldBuilder; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::state::ValueState; -use crate::interface::*; - -/// Local type alias to simplify the builder. -type EnterAction = Box<dyn FnMut() -> Vec<ClickAction>>; - -pub struct InputField<TEXT: Display + 'static> { - input_state: TrackedState<String>, - ghost_text: TEXT, - enter_action: EnterAction, - length: usize, - hidden: bool, - width_bound: DimensionBound, - state: ElementState, -} - -impl<TEXT: Display + 'static> InputField<TEXT> { - fn remove_character(&mut self) -> Vec<ClickAction> { - self.input_state.with_mut(|input_state| { - if input_state.is_empty() { - return ValueState::Unchanged(Vec::new()); - } - - input_state.pop(); - - ValueState::Mutated(vec![ClickAction::ChangeEvent(ChangeEvent::RENDER_WINDOW)]) - }) - } - - fn add_character(&mut self, character: char) -> Vec<ClickAction> { - self.input_state.with_mut(|input_state| { - if input_state.len() >= self.length { - return ValueState::Unchanged(Vec::new()); - } - - input_state.push(character); - - ValueState::Mutated(vec![ClickAction::ChangeEvent(ChangeEvent::RENDER_WINDOW)]) - }) - } -} - -impl<TEXT: Display + 'static> Element for InputField<TEXT> { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let size_bound = self.width_bound.add_height(theme.input.height_bound); - self.state.resolve(placement_resolver, &size_bound); - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _update: &mut bool) -> Vec<ClickAction> { - vec![ClickAction::FocusElement] - } - - fn input_character(&mut self, character: char) -> Vec<ClickAction> { - match character { - '\u{8}' | '\u{7f}' => self.remove_character(), - '\r' => (self.enter_action)(), - character => self.add_character(character), - } - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let input_state = self.input_state.borrow(); - let is_hovererd = self.is_element_self(hovered_element); - let is_focused = self.is_element_self(focused_element); - let text_offset = theme.input.text_offset.get(); - - let text = if input_state.is_empty() && !is_focused { - self.ghost_text.to_string() - } else if self.hidden { - input_state.chars().map(|_| '*').collect() - } else { - input_state.clone() - }; - - let background_color = if is_hovererd { - theme.input.hovered_background_color.get() - } else if is_focused { - theme.input.focused_background_color.get() - } else { - theme.input.background_color.get() - }; - - let text_color = if input_state.is_empty() && !is_focused { - theme.input.ghost_text_color.get() - } else if is_focused { - theme.input.focused_text_color.get() - } else { - theme.input.text_color.get() - }; - - renderer.render_background(theme.input.corner_radius.get(), background_color); - renderer.render_text(&text, text_offset, text_color, theme.input.font_size.get()); - - if is_focused { - let cursor_offset = (text_offset.left + theme.input.cursor_offset.get()) * interface_settings.scaling.get() - + renderer.get_text_dimensions(&text, theme.input.font_size.get(), f32::MAX).x; - - let cursor_position = ScreenPosition::only_left(cursor_offset); - let cursor_size = ScreenSize { - width: theme.input.cursor_width.get(), - height: self.state.cached_size.height, - }; - - renderer.render_rectangle( - cursor_position, - cursor_size, - CornerRadius::default(), - theme.input.text_color.get(), - ); - } - } -} diff --git a/src/interface/elements/miscellanious/picklist.rs b/src/interface/elements/miscellanious/picklist.rs deleted file mode 100644 index d11f9b59..00000000 --- a/src/interface/elements/miscellanious/picklist.rs +++ /dev/null @@ -1,196 +0,0 @@ -use procedural::{dimension_bound, size_bound}; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::{Element, *}; - -pub struct PickList<K, T, E> -where - K: Clone + AsRef<str> + 'static, - E: Clone + ElementEvent + 'static, - T: Clone + PartialEq + 'static, -{ - options: Vec<(K, T)>, - selected: Option<TrackedState<T>>, - event: Option<E>, - width_bound: Option<DimensionBound>, - state: ElementState, - latest_position: Rc<RefCell<ScreenPosition>>, - latest_size: Rc<RefCell<ScreenSize>>, -} - -// HACK: Workaround for Rust incorrect trait bounds when deriving Option<T> -// where T: !Default. -impl<K, T, E> Default for PickList<K, T, E> -where - K: Clone + AsRef<str> + 'static, - E: Clone + ElementEvent + 'static, - T: Clone + PartialEq + 'static, -{ - fn default() -> Self { - Self { - options: Default::default(), - selected: Default::default(), - event: Default::default(), - width_bound: Default::default(), - state: Default::default(), - latest_position: Rc::new(RefCell::new(ScreenPosition::default())), - latest_size: Rc::new(RefCell::new(ScreenSize::default())), - } - } -} - -impl<K, T, E> PickList<K, T, E> -where - K: Clone + AsRef<str> + 'static, - E: Clone + ElementEvent + 'static, - T: Clone + PartialEq + 'static, -{ - pub fn with_options(mut self, options: Vec<(K, T)>) -> Self { - self.options = options; - self - } - - pub fn with_selected(mut self, selected: TrackedState<T>) -> Self { - self.selected = Some(selected); - self - } - - pub fn with_event(mut self, event: E) -> Self { - self.event = Some(event); - self - } - - pub fn with_width(mut self, width_bound: DimensionBound) -> Self { - self.width_bound = Some(width_bound); - self - } -} - -impl<K, T, E> Element for PickList<K, T, E> -where - K: Clone + AsRef<str> + 'static, - E: Clone + ElementEvent + 'static, - T: Clone + PartialEq + 'static, -{ - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let size_bound = self - .width_bound - .as_ref() - .unwrap_or(&dimension_bound!(100%)) - .add_height(theme.button.height_bound); - - self.state.resolve(placement_resolver, &size_bound); - - *self.latest_size.borrow_mut() = self.state.cached_size; - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> { - let position_tracker = { - let latest_position = Rc::downgrade(&self.latest_position); - move || latest_position.upgrade().map(|position| *position.borrow()) - }; - - let size_tracker = { - let latest_size = Rc::downgrade(&self.latest_size); - move || latest_size.upgrade().map(|size| *size.borrow()) - }; - - let options = self - .options - .iter() - .cloned() - .map(|(text, option)| { - // FIX: What is the behavior here when slected is none? - let mut selected = self.selected.clone().unwrap(); - let mut event = self.event.clone(); - - ButtonBuilder::new() - .with_text(text) - .with_event(Box::new(move || { - selected.set(option.clone()); - let mut actions = vec![ClickAction::ClosePopup]; - - if let Some(event) = &mut event { - actions.extend(event.trigger()); - }; - - actions - })) - .build() - .wrap() - }) - .collect(); - - let element = ScrollView::new(options, size_bound!(100%, super > ? < super)) - .with_background_color(|theme| theme.button.background_color.get()) - .wrap(); - - vec![ClickAction::OpenPopup { - element, - position_tracker: Box::new(position_tracker), - size_tracker: Box::new(size_tracker), - }] - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let highlighted = self.is_element_self(hovered_element) || self.is_element_self(focused_element); - let background_color = match highlighted { - true => theme.button.hovered_background_color.get(), - false => theme.button.background_color.get(), - }; - - renderer.render_background(theme.button.corner_radius.get(), background_color); - - *self.latest_position.borrow_mut() = renderer.get_position(); - - let foreground_color = match highlighted { - true => theme.button.hovered_foreground_color.get(), - false => theme.button.foreground_color.get(), - }; - - // FIX: Don't unwrap. Fix logic - let current_state = self.selected.as_ref().map(|state| state.get()).unwrap(); - - if let Some((text, _)) = self.options.iter().find(|(_, value)| *value == current_state) { - renderer.render_text( - text.as_ref(), - theme.button.text_offset.get(), - foreground_color, - theme.button.font_size.get(), - ); - } - } -} diff --git a/src/interface/elements/miscellanious/slider.rs b/src/interface/elements/miscellanious/slider.rs deleted file mode 100644 index a37619b7..00000000 --- a/src/interface/elements/miscellanious/slider.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::cmp::PartialOrd; - -use derive_new::new; -use num::traits::NumOps; -use num::{clamp, NumCast, Zero}; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::{Element, *}; - -#[derive(new)] -pub struct Slider<T: Zero + NumOps + NumCast + Copy + PartialOrd + 'static> { - reference: &'static T, - minimum_value: T, - maximum_value: T, - change_event: Option<ChangeEvent>, - #[new(value = "T::zero()")] - cached_value: T, - #[new(default)] - state: ElementState, -} - -impl<T: Zero + NumOps + NumCast + Copy + PartialOrd + 'static> Element for Slider<T> { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - self.state.resolve(placement_resolver, &theme.slider.size_bound); - } - - fn update(&mut self) -> Option<ChangeEvent> { - let current_value = *self.reference; - - if self.cached_value != current_value { - self.cached_value = current_value; - return Some(ChangeEvent::RENDER_WINDOW); - } - - None - } - - fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - match mouse_mode { - MouseInputMode::None => self.state.hovered_element(mouse_position), - MouseInputMode::DragElement((element, _)) if self.is_element_self(Some(&*element.borrow())) => HoverInformation::Hovered, - _ => HoverInformation::Missed, - } - } - - fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> { - vec![ClickAction::DragElement] - } - - fn drag(&mut self, mouse_delta: ScreenPosition) -> Option<ChangeEvent> { - let total_range = self.maximum_value.to_f32().unwrap() - self.minimum_value.to_f32().unwrap(); - let raw_value = self.cached_value.to_f32().unwrap() + (mouse_delta.left * total_range * 0.005); - let new_value = clamp( - raw_value, - self.minimum_value.to_f32().unwrap(), - self.maximum_value.to_f32().unwrap(), - ); - - // SAFETY: Obviously this is totally unsafe, but considering this is a debug - // tool I think it's acceptable. - unsafe { - #[allow(invalid_reference_casting)] - std::ptr::write(self.reference as *const T as *mut T, T::from(new_value).unwrap()); - } - self.change_event - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - if self.is_element_self(hovered_element) { - renderer.render_background(theme.button.corner_radius.get(), theme.slider.background_color.get()); - } - - let bar_size = ScreenSize { - width: self.state.cached_size.width * 0.9, - height: self.state.cached_size.height / 4.0, - }; - let offset = ScreenPosition::from_size((self.state.cached_size - bar_size) / 2.0); - - renderer.render_rectangle(offset, bar_size, CornerRadius::uniform(0.5), theme.slider.rail_color.get()); - - let knob_size = ScreenSize { - width: 20.0 * interface_settings.scaling.get(), - height: self.state.cached_size.height * 0.8, - }; - let total_range = self.maximum_value - self.minimum_value; - let offset = ScreenPosition { - left: (self.state.cached_size.width - knob_size.width) / total_range.to_f32().unwrap() - * (self.cached_value.to_f32().unwrap() - self.minimum_value.to_f32().unwrap()), - top: (self.state.cached_size.height - knob_size.height) / 2.0, - }; - - renderer.render_rectangle(offset, knob_size, CornerRadius::uniform(4.0), theme.slider.knob_color.get()); - } -} diff --git a/src/interface/elements/miscellanious/static_label.rs b/src/interface/elements/miscellanious/static_label.rs deleted file mode 100644 index 81c04f3a..00000000 --- a/src/interface/elements/miscellanious/static_label.rs +++ /dev/null @@ -1,65 +0,0 @@ -use derive_new::new; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::interface::{Element, *}; - -#[derive(new)] -pub struct StaticLabel { - label: String, - #[new(default)] - state: ElementState, -} - -impl Element for StaticLabel { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let mut size_bound = theme.label.size_bound; - - let size = placement_resolver.get_text_dimensions( - &self.label, - theme.label.font_size.get(), - theme.label.text_offset.get(), - interface_settings.scaling.get(), - placement_resolver.get_available().width / 2.0, // TODO: make better - ); - - size_bound.height = Dimension::Absolute(f32::max(size.y / interface_settings.scaling.get(), 14.0)); // TODO: make better - - self.state.resolve(placement_resolver, &size_bound); - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - renderer.render_background(theme.label.corner_radius.get(), theme.label.background_color.get()); - - renderer.render_text( - &self.label, - theme.label.text_offset.get(), - theme.label.foreground_color.get(), - theme.label.font_size.get(), - ); - } -} diff --git a/src/interface/elements/miscellanious/text.rs b/src/interface/elements/miscellanious/text.rs deleted file mode 100644 index fafcdf47..00000000 --- a/src/interface/elements/miscellanious/text.rs +++ /dev/null @@ -1,105 +0,0 @@ -use procedural::dimension_bound; - -use crate::graphics::{Color, InterfaceRenderer, Renderer}; -use crate::interface::*; - -#[derive(Default)] -pub struct Text<T: AsRef<str> + 'static> { - text: Option<T>, - foreground_color: Option<ColorSelector>, - width_bound: Option<DimensionBound>, - font_size: Option<FontSizeSelector>, - state: ElementState, -} - -impl<T: AsRef<str> + 'static> Text<T> { - pub fn with_text(mut self, text: T) -> Self { - self.text = Some(text); - self - } - - pub fn with_foreground_color(mut self, foreground_color: impl Fn(&InterfaceTheme) -> Color + 'static) -> Self { - self.foreground_color = Some(Box::new(foreground_color)); - self - } - - pub fn with_font_size(mut self, font_size: impl Fn(&InterfaceTheme) -> f32 + 'static) -> Self { - self.font_size = Some(Box::new(font_size)); - self - } - - pub fn with_width(mut self, width_bound: DimensionBound) -> Self { - self.width_bound = Some(width_bound); - self - } - - fn get_font_size(&self, theme: &InterfaceTheme) -> f32 { - self.font_size - .as_ref() - .map(|closure| closure(theme)) - .unwrap_or(theme.button.font_size.get()) - } -} - -impl<T: AsRef<str> + 'static> Element for Text<T> { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - let height_bound = DimensionBound { - size: Dimension::Absolute(self.get_font_size(theme)), - minimum_size: None, - maximum_size: None, - }; - - let size_bound = self - .width_bound - .as_ref() - .unwrap_or(&dimension_bound!(100%)) - .add_height(height_bound); - - self.state.resolve(placement_resolver, &size_bound); - } - - fn is_focusable(&self) -> bool { - false - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - let foreground_color = self - .foreground_color - .as_ref() - .map(|closure| closure(theme)) - .unwrap_or(theme.button.foreground_color.get()); - - let text = self.text.as_ref().unwrap(); - renderer.render_text( - text.as_ref(), - ScreenPosition::default(), - foreground_color, - self.get_font_size(theme), - ); - } -} diff --git a/src/interface/elements/values/color.rs b/src/interface/elements/values/color.rs deleted file mode 100644 index eb0eb44c..00000000 --- a/src/interface/elements/values/color.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::graphics::{Color, InterfaceRenderer, Renderer}; -use crate::interface::{Element, *}; - -pub struct ColorValue { - color: Color, - display: String, - state: ElementState, -} - -impl ColorValue { - pub fn new(color: Color) -> Self { - let display = format!( - "{}, {}, {}, {}", - color.red_as_u8(), - color.green_as_u8(), - color.blue_as_u8(), - color.alpha_as_u8() - ); - let state = ElementState::default(); - - Self { color, display, state } - } -} - -impl Element for ColorValue { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - self.state.resolve(placement_resolver, &theme.value.size_bound); - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - renderer.render_background(theme.value.corner_radius.get(), self.color); - - renderer.render_text( - &self.display, - theme.value.text_offset.get(), - self.color.invert(), - theme.value.font_size.get(), - ); - } -} diff --git a/src/interface/elements/values/mod.rs b/src/interface/elements/values/mod.rs deleted file mode 100644 index 10c408d8..00000000 --- a/src/interface/elements/values/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod color; -mod mutable; -mod string; - -pub use self::color::ColorValue; -pub use self::mutable::*; -pub use self::string::StringValue; diff --git a/src/interface/elements/values/quaternion.rs b/src/interface/elements/values/quaternion.rs deleted file mode 100644 index 4996b3d3..00000000 --- a/src/interface/elements/values/quaternion.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::fmt::Display; - -use cgmath::Quaternion; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::interface::{Element, *}; - -pub struct QuaternionValue<T: Display> { - value: Quaternion<T>, - display: String, - state: ElementState, -} - -impl<T: Display> QuaternionValue<T> { - - pub fn new(value: Quaternion<T>) -> Self { - - let display = format!( - "{:.1}, {:.1}, {:.1} - {:.1}", - value.v.x, value.v.y, value.v.z, value.s - ); - let state = ElementState::default(); - - Self { value, display, state } - } -} - -impl<T: Display> Element for QuaternionValue<T> { - - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &Theme) { - self.state.resolve(placement_resolver, &theme.value.size_bound); - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &Theme, - parent_position: Position, - screen_clip: ClipSize, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _second_theme: bool, - ) { - - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - renderer.render_background(theme.value.corner_radius.get(), theme.value.hovered_background_color.get()); - - renderer.render_text( - &self.display, - theme.value.text_offset.get(), - theme.value.foreground_color.get(), - theme.value.font_size.get(), - ); - } -} diff --git a/src/interface/elements/values/string.rs b/src/interface/elements/values/string.rs deleted file mode 100644 index 3ee7266e..00000000 --- a/src/interface/elements/values/string.rs +++ /dev/null @@ -1,53 +0,0 @@ -use derive_new::new; - -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::interface::{Element, *}; - -#[derive(new)] -pub struct StringValue { - value: String, - #[new(default)] - state: ElementState, -} - -impl Element for StringValue { - fn get_state(&self) -> &ElementState { - &self.state - } - - fn get_state_mut(&mut self) -> &mut ElementState { - &mut self.state - } - - fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) { - self.state.resolve(placement_resolver, &theme.value.size_bound); - } - - fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - _state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - parent_position: ScreenPosition, - screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { - let mut renderer = self - .state - .element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip); - - renderer.render_background(theme.value.corner_radius.get(), theme.value.hovered_background_color.get()); - - renderer.render_text( - &self.value, - theme.value.text_offset.get(), - theme.value.foreground_color.get(), - theme.value.font_size.get(), - ); - } -} diff --git a/src/interface/event/action.rs b/src/interface/event/action.rs deleted file mode 100644 index 7e33a7e8..00000000 --- a/src/interface/event/action.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::{ItemSource, SkillSource}; -use crate::input::UserEvent; -use crate::interface::{ChangeEvent, ElementCell, FocusMode, PrototypeWindow, ScreenPosition, ScreenSize, Tracker}; -use crate::inventory::{Item, Skill}; - -pub enum ClickAction { - FocusElement, - FocusNext(FocusMode), - ChangeEvent(ChangeEvent), - Event(UserEvent), - DragElement, - MoveItem(ItemSource, Item), - MoveSkill(SkillSource, Skill), - MoveInterface, - OpenWindow(Box<dyn PrototypeWindow>), - CloseWindow, - OpenPopup { - element: ElementCell, - position_tracker: Tracker<ScreenPosition>, - size_tracker: Tracker<ScreenSize>, - }, - ClosePopup, -} diff --git a/src/interface/event/hover.rs b/src/interface/event/hover.rs deleted file mode 100644 index 3d3ee680..00000000 --- a/src/interface/event/hover.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::interface::ElementCell; - -pub enum HoverInformation { - Element(ElementCell), - Hovered, - Missed, -} diff --git a/src/interface/event/item.rs b/src/interface/event/item.rs deleted file mode 100644 index 214e151e..00000000 --- a/src/interface/event/item.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::inventory::Item; -use crate::network::EquipPosition; - -#[derive(Clone, Copy, Debug)] -pub enum ItemSource { - Inventory, - Equipment { position: EquipPosition }, -} - -#[derive(Debug, Clone)] -pub struct ItemMove { - pub source: ItemSource, - pub destination: ItemSource, - pub item: Item, -} diff --git a/src/interface/event/skill.rs b/src/interface/event/skill.rs deleted file mode 100644 index 366e5927..00000000 --- a/src/interface/event/skill.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::input::HotbarSlot; -use crate::inventory::Skill; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum SkillSource { - SkillTree, - Hotbar { slot: HotbarSlot }, -} - -#[derive(Clone, Debug)] -pub struct SkillMove { - pub source: SkillSource, - pub destination: SkillSource, - pub skill: Skill, -} diff --git a/src/interface/layout/resolver.rs b/src/interface/layout/resolver.rs deleted file mode 100644 index 03010448..00000000 --- a/src/interface/layout/resolver.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use cgmath::Vector2; - -use super::bound::ParentLimits; -use crate::interface::{PartialScreenSize, ScreenPosition, ScreenSize, SizeBound}; -use crate::loaders::FontLoader; - -const ELEMENT_THRESHHOLD: f32 = 1.0000; -const REMAINDER_THRESHHOLD: f32 = 0.0001; - -pub struct PlacementResolver { - font_loader: Rc<RefCell<FontLoader>>, - available_space: PartialScreenSize, - parent_limits: ParentLimits, - base_position: ScreenPosition, - horizontal_accumulator: f32, - vertical_offset: f32, - total_height: f32, - border: ScreenSize, - gaps: ScreenSize, - scaling: f32, -} - -impl PlacementResolver { - pub fn new( - font_loader: Rc<RefCell<FontLoader>>, - screen_size: ScreenSize, - mut window_size: ScreenSize, - size_bound: &SizeBound, - border: ScreenSize, - gaps: ScreenSize, - scaling: f32, - ) -> Self { - window_size -= border * scaling * 2.0; - - // NOTE: I'm not enirely sure why we need to subtract one border here, but if we - // don't the `super` bound is too big. - let parent_limits = ParentLimits::from_bound(size_bound, screen_size - border * scaling, scaling); - let base_position = ScreenPosition::from_size(border * scaling); - let horizontal_accumulator = 0.0; - let vertical_offset = 0.0; - let total_height = 0.0; - - let height = (!size_bound.height.is_flexible()).then_some(window_size.height); - let available_space = PartialScreenSize::new(window_size.width, height); - - Self { - font_loader, - available_space, - parent_limits, - base_position, - horizontal_accumulator, - total_height, - vertical_offset, - border, - gaps, - scaling, - } - } - - pub fn derive( - &mut self, - size_bound: &SizeBound, - offset: ScreenPosition, - border: ScreenSize, - ) -> (Self, PartialScreenSize, ScreenPosition) { - let (size, position) = self.allocate(size_bound); - - let border_with_offset = (offset + border * self.scaling * 2.0) - ScreenPosition::default(); - let available_space = PartialScreenSize { - width: size.width - border_with_offset.width, - height: size.height.map(|height| height - border_with_offset.height), - }; - - let font_loader = self.font_loader.clone(); - let unusable_space = (position + border_with_offset) - ScreenPosition::default(); - let parent_limits = self.parent_limits.derive(size_bound, available_space, unusable_space, self.scaling); - let base_position = offset + border * self.scaling; - let horizontal_accumulator = 0.0; - let vertical_offset = 0.0; - let total_height = 0.0; - let gaps = self.gaps; - let scaling = self.scaling; - - let derived_resolver = Self { - font_loader, - available_space, - parent_limits, - base_position, - horizontal_accumulator, - total_height, - vertical_offset, - border, - gaps, - scaling, - }; - - (derived_resolver, size, position) - } - - pub fn get_text_dimensions( - &self, - text: &str, - font_size: f32, - text_offset: ScreenPosition, - scaling: f32, - available_width: f32, - ) -> Vector2<f32> { - self.font_loader - .borrow() - .get_text_dimensions(text, font_size * scaling, available_width - text_offset.left * scaling) - } - - pub fn set_gaps(&mut self, gaps: ScreenSize) { - self.gaps = gaps; - } - - pub fn get_available(&self) -> PartialScreenSize { - self.available_space - } - - pub fn get_remaining(&self) -> PartialScreenSize { - let remaining_width = self.available_space.width - self.horizontal_accumulator; - let remaining_height = self - .available_space - .height - .map(|height| height - self.total_height - self.vertical_offset); - - PartialScreenSize::new(remaining_width, remaining_height) - } - - pub fn newline(&mut self) { - self.total_height += self.vertical_offset + self.gaps.height * self.scaling; - self.base_position.top += self.vertical_offset + self.gaps.height * self.scaling; - self.horizontal_accumulator = 0.0; - self.vertical_offset = 0.0; - } - - pub fn register_height(&mut self, height: f32) { - self.vertical_offset = f32::max(self.vertical_offset, height); - } - - pub fn allocate(&mut self, size_bound: &SizeBound) -> (PartialScreenSize, ScreenPosition) { - let is_width_absolute = size_bound.width.is_absolute(); - let gaps_add = match is_width_absolute { - true => self.gaps.width * 2.0, - false => 0.0, - }; - - let mut remaining = self.get_remaining(); - let mut size = size_bound.resolve_element(self.available_space, remaining, &self.parent_limits, self.scaling); - let mut gaps_subtract = 0.0; - - if remaining.width < size.width - REMAINDER_THRESHHOLD { - self.newline(); - remaining = self.get_remaining(); - - if size_bound.width.is_remaining() || size_bound.height.is_remaining() { - size = size_bound.resolve_element(self.available_space, remaining, &self.parent_limits, self.scaling); - } - - size.width = f32::min(size.width, self.available_space.width); - } - - if self.horizontal_accumulator > ELEMENT_THRESHHOLD { - match is_width_absolute { - true => {} - false => gaps_subtract += self.gaps.width * self.scaling, - } - } - - let position = ScreenPosition { - left: self.base_position.left + self.horizontal_accumulator + gaps_subtract, - top: self.base_position.top, - }; - - self.horizontal_accumulator += size.width + gaps_add; - - if let Some(height) = size.height { - self.register_height(height); - } - - if remaining.width - size.width > ELEMENT_THRESHHOLD { - match is_width_absolute { - true => {} - false => gaps_subtract += self.gaps.width * self.scaling, - } - } - - size.width -= gaps_subtract; - (size, position) - } - - pub fn allocate_right(&mut self, size_bound: &SizeBound) -> (PartialScreenSize, ScreenPosition) { - let mut remaining = self.get_remaining(); - let mut size = size_bound.resolve_element(self.available_space, remaining, &self.parent_limits, self.scaling); - - if remaining.width < size.width - REMAINDER_THRESHHOLD + self.gaps.width * self.scaling { - self.newline(); - remaining = self.get_remaining(); - - if size_bound.width.is_remaining() || size_bound.height.is_remaining() { - size = size_bound.resolve_element(self.available_space, remaining, &self.parent_limits, self.scaling); - } - } - - let position = ScreenPosition { - left: self.base_position.left + (self.available_space.width - size.width - self.gaps.width * self.scaling), - top: self.base_position.top, - }; - - self.horizontal_accumulator += remaining.width; - - if let Some(height) = size.height { - self.register_height(height); - } - - (size, position) - } - - pub fn get_parent_limits(&self) -> ParentLimits { - self.parent_limits - } - - pub fn final_height(self) -> f32 { - self.total_height + self.vertical_offset + self.border.height * self.scaling - } -} diff --git a/src/interface/mod.rs b/src/interface/mod.rs deleted file mode 100644 index 0653dc0c..00000000 --- a/src/interface/mod.rs +++ /dev/null @@ -1,789 +0,0 @@ -mod event; -mod layout; -mod provider; -mod settings; -mod state; -mod theme; -#[macro_use] -mod elements; -pub mod builder; -mod cursor; -mod windows; - -use std::cell::RefCell; -use std::marker::{ConstParamTy, PhantomData}; -use std::rc::Rc; - -use derive_new::new; -use option_ext::OptionExt; -use procedural::profile; - -pub use self::cursor::*; -pub use self::elements::*; -pub use self::event::*; -pub use self::layout::*; -pub use self::provider::StateProvider; -pub use self::settings::InterfaceSettings; -pub use self::state::{Remote, TrackedState, TrackedStateTake, ValueState}; -pub use self::theme::{GameTheme, InterfaceTheme}; -use self::theme::{Main, Menu, ThemeSelector, Themes}; -pub use self::windows::*; -#[cfg(feature = "debug")] -use crate::debug::*; -use crate::graphics::{Color, DeferredRenderer, InterfaceRenderer, Renderer}; -use crate::input::{FocusState, Grabbed, MouseInputMode, UserEvent}; -use crate::loaders::{ActionLoader, FontLoader, GameFileLoader, SpriteLoader}; -use crate::network::{ClientTick, EntityId}; - -// TODO: move this -pub type Selector = Box<dyn Fn() -> bool>; -pub type ColorSelector = Box<dyn Fn(&InterfaceTheme) -> Color>; -pub type FontSizeSelector = Box<dyn Fn(&InterfaceTheme) -> f32>; - -pub trait ElementEvent { - fn trigger(&mut self) -> Vec<ClickAction>; -} - -impl<F> ElementEvent for Box<F> -where - F: FnMut() -> Vec<ClickAction> + 'static, -{ - fn trigger(&mut self) -> Vec<ClickAction> { - self() - } -} - -impl ElementEvent for UserEvent { - fn trigger(&mut self) -> Vec<ClickAction> { - vec![ClickAction::Event(self.clone())] - } -} - -#[derive(new)] -struct DialogHandle { - elements: TrackedState<Vec<DialogElement>>, - clear: bool, -} - -#[derive(Clone)] -struct PerWindow; - -#[derive(Clone)] -struct PostUpdate<T> { - resolve: bool, - render: bool, - marker: PhantomData<T>, -} - -impl<T> PostUpdate<T> { - pub fn new() -> Self { - Self { - resolve: false, - render: false, - marker: PhantomData, - } - } - - pub fn render(&mut self) { - self.render = true; - } - - pub fn resolve(&mut self) { - self.resolve = true; - } - - pub fn with_render(mut self) -> Self { - self.render = true; - self - } - - pub fn with_resolve(mut self) -> Self { - self.resolve = true; - self - } - - pub fn needs_render(&self) -> bool { - self.render - } - - pub fn needs_resolve(&self) -> bool { - self.resolve - } - - pub fn take_render(&mut self) -> bool { - let state = self.render; - self.render = false; - state - } - - pub fn take_resolve(&mut self) -> bool { - let state = self.resolve; - self.resolve = false; - state - } -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub enum ThemeKind { - Menu, - #[default] - Main, - Game, -} - -impl ConstParamTy for ThemeKind {} - -pub type Tracker<T> = Box<dyn Fn() -> Option<T>>; - -pub struct Interface { - windows: Vec<(Window, PostUpdate<PerWindow>)>, - window_cache: WindowCache, - interface_settings: InterfaceSettings, - available_space: ScreenSize, - themes: Themes, - dialog_handle: Option<DialogHandle>, - mouse_cursor: MouseCursor, - mouse_cursor_hidden: bool, - post_update: PostUpdate<Self>, -} - -impl Interface { - pub fn new( - game_file_loader: &mut GameFileLoader, - sprite_loader: &mut SpriteLoader, - action_loader: &mut ActionLoader, - available_space: ScreenSize, - ) -> Self { - let window_cache = WindowCache::new(); - let interface_settings = InterfaceSettings::new(); - let themes = Themes { - theme_selector: ThemeSelector, - menu: InterfaceTheme::new::<Menu>(interface_settings.menu_theme.get_file()), - main: InterfaceTheme::new::<Main>(interface_settings.main_theme.get_file()), - game: GameTheme::new(interface_settings.game_theme.get_file()), - }; - let dialog_handle = None; - let mouse_cursor = MouseCursor::new(game_file_loader, sprite_loader, action_loader); - let mouse_cursor_hidden = false; - // NOTE: We need to initially clear the interface buffer - let post_update = PostUpdate::new().with_render(); - - Self { - windows: Vec::new(), - window_cache, - interface_settings, - available_space, - themes, - dialog_handle, - mouse_cursor, - mouse_cursor_hidden, - post_update, - } - } - - pub fn set_theme_file(&mut self, theme_file: String, theme_kind: ThemeKind) { - match theme_kind { - ThemeKind::Menu => self.interface_settings.menu_theme.set_file(theme_file), - ThemeKind::Main => self.interface_settings.main_theme.set_file(theme_file), - ThemeKind::Game => self.interface_settings.game_theme.set_file(theme_file), - } - } - - pub fn get_game_theme(&self) -> &GameTheme { - &self.themes.game - } - - #[profile] - pub fn save_theme(&self, kind: ThemeKind) { - match kind { - ThemeKind::Menu => self.themes.menu.save(self.interface_settings.menu_theme.get_file()), - ThemeKind::Main => self.themes.main.save(self.interface_settings.main_theme.get_file()), - ThemeKind::Game => self.themes.game.save(self.interface_settings.game_theme.get_file()), - } - } - - #[profile] - pub fn reload_theme(&mut self, kind: ThemeKind) { - match kind { - ThemeKind::Menu => self.themes.menu.reload::<Menu>(self.interface_settings.menu_theme.get_file()), - ThemeKind::Main => self.themes.main.reload::<Main>(self.interface_settings.main_theme.get_file()), - ThemeKind::Game => self.themes.game.reload(self.interface_settings.game_theme.get_file()), - } - - self.post_update.resolve(); - } - - pub fn schedule_render(&mut self) { - self.post_update.render(); - } - - pub fn schedule_render_window(&mut self, window_index: usize) { - if window_index < self.windows.len() { - let (_, post_update) = &mut self.windows[window_index]; - post_update.render(); - } - } - - // TODO: this is just a workaround until i find a better solution to make the - // cursor always look correct. - pub fn set_start_time(&mut self, client_tick: ClientTick) { - self.mouse_cursor.set_start_time(client_tick); - } - - /// The update and render functions take care of merging the window specific - /// flags with the interface wide flags. - fn handle_change_event(post_update: &mut PostUpdate<Self>, window_post_update: &mut PostUpdate<PerWindow>, change_event: ChangeEvent) { - if change_event.contains(ChangeEvent::RENDER_WINDOW) { - window_post_update.render(); - } - - if change_event.contains(ChangeEvent::RESOLVE_WINDOW) { - window_post_update.resolve(); - } - - if change_event.contains(ChangeEvent::RENDER) { - post_update.render(); - } - - if change_event.contains(ChangeEvent::RESOLVE) { - post_update.resolve(); - } - } - - #[profile("update user interface")] - pub fn update(&mut self, font_loader: Rc<RefCell<FontLoader>>, focus_state: &mut FocusState, client_tick: ClientTick) -> (bool, bool) { - self.mouse_cursor.update(client_tick); - - for (window, post_update) in &mut self.windows { - #[cfg(feature = "debug")] - - profile_block!("update window"); - - if let Some(change_event) = window.update() { - Self::handle_change_event(&mut self.post_update, post_update, change_event); - } - } - - let mut restore_focus = false; - - for (window_index, (window, post_update)) in self.windows.iter_mut().enumerate() { - if self.post_update.needs_resolve() || post_update.take_resolve() { - #[cfg(feature = "debug")] - - profile_block!("resolve window"); - - let (_position, previous_size) = window.get_area(); - let theme = match window.get_theme_kind() { - ThemeKind::Menu => &self.themes.menu, - ThemeKind::Main => &self.themes.main, - _ => panic!(), - }; - - let (window_class, new_position, new_size) = - window.resolve(font_loader.clone(), &self.interface_settings, theme, self.available_space); - - // should only ever be the last window - if let Some(focused_index) = focus_state.focused_window() - && focused_index == window_index - { - restore_focus = true; - } - - if let Some(window_class) = window_class { - self.window_cache.register_window(window_class, new_position, new_size); - } - - // NOTE: If the window got smaller, we need to re-render the entire interface. - // If it got bigger, we can just draw over the previous frame. - match previous_size.width > new_size.width || previous_size.height > new_size.height { - true => self.post_update.render(), - false => post_update.render(), - } - } - } - - if restore_focus { - self.restore_focus(focus_state); - } - - if self.post_update.take_resolve() { - self.post_update.render(); - } - - if !self.post_update.needs_render() { - // We profile this block rather than the flag function itself because it calls - // itself recursively - #[cfg(feature = "debug")] - profile_block!("flag render windows"); - - self.flag_render_windows(0, None); - } - - let render_interface = self.post_update.needs_render(); - let render_window = self.post_update.needs_render() | self.windows.iter().any(|(_window, post_update)| post_update.needs_render()); - - (render_interface, render_window) - } - - pub fn update_window_size(&mut self, screen_size: ScreenSize) { - self.available_space = screen_size; - self.post_update.resolve(); - } - - #[profile("get hovered element")] - pub fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> (Option<ElementCell>, Option<usize>) { - for (window_index, (window, _)) in self.windows.iter().enumerate().rev() { - match window.hovered_element(mouse_position, mouse_mode) { - HoverInformation::Element(hovered_element) => return (Some(hovered_element), Some(window_index)), - HoverInformation::Hovered => return (None, Some(window_index)), - HoverInformation::Missed => {} - } - } - - (None, None) - } - - #[profile] - pub fn move_window_to_top(&mut self, window_index: usize) -> usize { - let (window, post_update) = self.windows.remove(window_index); - let new_window_index = self.windows.len(); - - self.windows.push((window, post_update.with_render())); - - new_window_index - } - - #[profile] - pub fn left_click_element(&mut self, hovered_element: &ElementCell, window_index: usize) -> Vec<ClickAction> { - let (_, post_update) = &mut self.windows[window_index]; - let mut resolve = false; - - let action = hovered_element.borrow_mut().left_click(&mut resolve); // TODO: add same change_event check as for input character ? - - if resolve { - post_update.resolve(); - } - - action - } - - #[profile] - pub fn right_click_element(&mut self, hovered_element: &ElementCell, window_index: usize) -> Vec<ClickAction> { - let (_, post_update) = &mut self.windows[window_index]; - let mut resolve = false; - - let action = hovered_element.borrow_mut().right_click(&mut resolve); // TODO: add same change_event check as for input character ? - - if resolve { - post_update.resolve(); - } - - action - } - - #[profile] - pub fn drag_element(&mut self, element: &ElementCell, _window_index: usize, mouse_delta: ScreenPosition) { - //let (_window, post_update) = &mut self.windows[window_index]; - - if let Some(change_event) = element.borrow_mut().drag(mouse_delta) { - // TODO: Use the window post_update here (?) - Self::handle_change_event(&mut self.post_update, &mut PostUpdate::new(), change_event); - } - } - - #[profile] - pub fn scroll_element(&mut self, element: &ElementCell, window_index: usize, scroll_delta: f32) { - let (_, post_update) = &mut self.windows[window_index]; - - if let Some(change_event) = element.borrow_mut().scroll(scroll_delta) { - Self::handle_change_event(&mut self.post_update, post_update, change_event); - } - } - - #[profile] - pub fn input_character_element(&mut self, element: &ElementCell, window_index: usize, character: char) -> Vec<ClickAction> { - let (_, post_update) = &mut self.windows[window_index]; - let mut propagated_actions = Vec::new(); - - for action in element.borrow_mut().input_character(character) { - match action { - ClickAction::ChangeEvent(change_event) => Self::handle_change_event(&mut self.post_update, post_update, change_event), - other => propagated_actions.push(other), - } - } - - propagated_actions - } - - #[profile] - pub fn move_window(&mut self, window_index: usize, offset: ScreenPosition) { - if let Some((window_class, position)) = self.windows[window_index].0.offset(self.available_space, offset) { - self.window_cache.update_position(window_class, position); - } - - self.post_update.render(); - } - - #[profile] - pub fn resize_window(&mut self, window_index: usize, growth: ScreenSize) { - let (window, post_update) = &mut self.windows[window_index]; - - let theme = match window.get_theme_kind() { - ThemeKind::Menu => &self.themes.menu, - ThemeKind::Main => &self.themes.main, - _ => panic!(), - }; - let (_position, previous_size) = window.get_area(); - - let (window_class, new_size) = window.resize(&self.interface_settings, theme, self.available_space, growth); - - if previous_size != new_size { - if let Some(window_class) = window_class { - self.window_cache.update_size(window_class, new_size); - } - - post_update.resolve(); - - if previous_size.width > new_size.width || previous_size.height > new_size.height { - self.post_update.render(); - } - } - } - - /// This function is solely responsible for making sure that trying to - /// re-render a window with transparency will result in re-rendering the - /// entire interface. This serves as a single point of truth and simplifies - /// the rest of the code. - fn flag_render_windows(&mut self, start_index: usize, area: Option<(ScreenPosition, ScreenSize)>) { - for window_index in start_index..self.windows.len() { - let needs_render = self.windows[window_index].1.needs_render(); - let is_hovering = |(position, scale)| self.windows[window_index].0.hovers_area(position, scale); - - if needs_render || area.map(is_hovering).unwrap_or(false) { - let (position, scale) = { - let (window, post_update) = &mut self.windows[window_index]; - - if window.has_transparency(&self.themes.main) { - self.post_update.render(); - return; - } - - post_update.render(); - window.get_area() - }; - - self.flag_render_windows(window_index + 1, Some((position, scale))); - } - } - } - - #[profile("render user interface")] - pub fn render( - &mut self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - hovered_element: Option<ElementCell>, - focused_element: Option<ElementCell>, - mouse_mode: &MouseInputMode, - ) { - let hovered_element = hovered_element.map(|element| unsafe { &*element.as_ptr() }); - let focused_element = focused_element.map(|element| unsafe { &*element.as_ptr() }); - - for (window, post_update) in &mut self.windows { - if post_update.take_render() || self.post_update.needs_render() { - #[cfg(feature = "debug")] - profile_block!("render window"); - - let theme = match window.get_theme_kind() { - ThemeKind::Menu => &self.themes.menu, - ThemeKind::Main => &self.themes.main, - _ => panic!(), - }; - - window.render( - render_target, - renderer, - state_provider, - &self.interface_settings, - theme, - hovered_element, - focused_element, - mouse_mode, - ); - } - } - - self.post_update.take_render(); - } - - #[profile] - pub fn render_hover_text( - &self, - render_target: &mut <DeferredRenderer as Renderer>::Target, - renderer: &DeferredRenderer, - text: &str, - mouse_position: ScreenPosition, - ) { - let offset = ScreenPosition { - left: text.len() as f32 * -3.0, - top: 20.0, - }; - - renderer.render_text( - render_target, - text, - mouse_position + offset + ScreenPosition::uniform(1.0), - Color::monochrome_u8(0), - 12.0, - ); // TODO: move variables into theme - - renderer.render_text(render_target, text, mouse_position + offset, Color::monochrome_u8(255), 12.0); // move variables into theme - } - - #[profile] - #[cfg(feature = "debug")] - pub fn render_frames_per_second( - &self, - render_target: &mut <DeferredRenderer as Renderer>::Target, - renderer: &DeferredRenderer, - frames_per_second: usize, - ) { - renderer.render_text( - render_target, - &frames_per_second.to_string(), - self.themes.game.overlay.text_offset.get() * self.interface_settings.scaling.get(), - self.themes.game.overlay.foreground_color.get(), - self.themes.game.overlay.font_size.get() * self.interface_settings.scaling.get(), - ); - } - - #[profile] - pub fn render_mouse_cursor( - &self, - render_target: &mut <DeferredRenderer as Renderer>::Target, - renderer: &DeferredRenderer, - mouse_position: ScreenPosition, - grabbed: Option<Grabbed>, - ) { - if !self.mouse_cursor_hidden { - #[cfg(feature = "debug")] - profile_block!("render mouse cursor"); - - self.mouse_cursor.render( - render_target, - renderer, - mouse_position, - grabbed, - self.themes.game.cursor.color.get(), - &self.interface_settings, - ); - } - } - - #[profile("check window exists")] - fn window_exists(&self, window_class: Option<&str>) -> bool { - match window_class { - Some(window_class) => self.windows.iter().any(|window| { - window - .0 - .get_window_class() - .map_or(false, |other_window_class| window_class == other_window_class) - }), - None => false, - } - } - - fn open_new_window(&mut self, focus_state: &mut FocusState, window: Window) { - self.windows.push((window, PostUpdate::new().with_resolve())); - focus_state.set_focused_window(self.windows.len() - 1); - } - - #[profile] - pub fn open_window(&mut self, focus_state: &mut FocusState, prototype_window: &dyn PrototypeWindow) { - if !self.window_exists(prototype_window.window_class()) { - let window = prototype_window.to_window(&self.window_cache, &self.interface_settings, self.available_space); - self.open_new_window(focus_state, window); - } - } - - #[profile] - pub fn open_popup( - &mut self, - element: ElementCell, - position_tracker: Tracker<ScreenPosition>, - size_tracker: Tracker<ScreenSize>, - window_index: usize, - ) { - let entry = &mut self.windows[window_index]; - entry.0.open_popup(element, position_tracker, size_tracker); - entry.1.resolve(); - } - - #[profile] - pub fn close_popup(&mut self, window_index: usize) { - let entry = &mut self.windows[window_index]; - entry.0.close_popup(); - entry.1.render(); - } - - #[profile] - pub fn open_dialog_window(&mut self, focus_state: &mut FocusState, text: String, npc_id: EntityId) { - if let Some(dialog_handle) = &mut self.dialog_handle { - dialog_handle.elements.with_mut(|elements| { - if dialog_handle.clear { - elements.clear(); - dialog_handle.clear = false; - } - - elements.push(DialogElement::Text(text)); - ValueState::Mutated(()) - }); - } else { - let (window, elements) = DialogWindow::new(text, npc_id); - self.dialog_handle = Some(DialogHandle::new(elements, false)); - self.open_window(focus_state, &window); - } - } - - #[profile] - pub fn add_next_button(&mut self) { - if let Some(dialog_handle) = &mut self.dialog_handle { - dialog_handle.elements.push(DialogElement::NextButton); - dialog_handle.clear = true; - } - } - - #[profile] - pub fn add_close_button(&mut self) { - if let Some(dialog_handle) = &mut self.dialog_handle { - dialog_handle.elements.with_mut(|elements| { - elements.retain(|element| *element != DialogElement::NextButton); - elements.push(DialogElement::CloseButton); - ValueState::Mutated(()) - }); - } - } - - #[profile] - pub fn add_choice_buttons(&mut self, choices: Vec<String>) { - if let Some(dialog_handle) = &mut self.dialog_handle { - dialog_handle.elements.with_mut(move |elements| { - elements.retain(|element| *element != DialogElement::NextButton); - - choices - .into_iter() - .enumerate() - .for_each(|(index, choice)| elements.push(DialogElement::ChoiceButton(choice, index as i8 + 1))); - - ValueState::Mutated(()) - }); - } - } - - pub fn handle_result<T>(&mut self, focus_state: &mut FocusState, result: Result<T, String>) { - if let Err(message) = result { - self.open_window(focus_state, &ErrorWindow::new(message)); - } - } - - #[profile] - #[cfg(feature = "debug")] - pub fn open_theme_viewer_window(&mut self, focus_state: &mut FocusState) { - if !self.window_exists(self.themes.window_class()) { - let window = self - .themes - .to_window(&self.window_cache, &self.interface_settings, self.available_space); - - self.open_new_window(focus_state, window); - } - } - - #[profile] - pub fn close_window(&mut self, focus_state: &mut FocusState, window_index: usize) { - let (window, ..) = self.windows.remove(window_index); - self.post_update.render(); - - // drop window in another thread to avoid frame drops when deallocation a large - // amount of elements - std::thread::spawn(move || drop(window)); - - // TODO: only if tab mode - self.restore_focus(focus_state); - } - - pub fn get_window(&self, window_index: usize) -> &Window { - &self.windows[window_index].0 - } - - #[profile] - pub fn close_window_with_class(&mut self, focus_state: &mut FocusState, window_class: &str) { - let index_from_back = self - .windows - .iter() - .rev() - .map(|(window, ..)| window.get_window_class()) - .position(|class_option| class_option.contains(&window_class)) - .unwrap(); - let index = self.windows.len() - 1 - index_from_back; - - self.close_window(focus_state, index); - } - - #[profile] - pub fn close_dialog_window(&mut self, focus_state: &mut FocusState) { - self.close_window_with_class(focus_state, DialogWindow::WINDOW_CLASS); - self.dialog_handle = None; - } - - #[profile] - pub fn close_all_windows_except(&mut self, focus_state: &mut FocusState) { - for index in (0..self.windows.len()).rev() { - if self.windows[index] - .0 - .get_window_class() - .map(|class| class != "theme_viewer" && class != "profiler" && class != "network") // HACK: don't hardcode - .unwrap_or(true) - { - self.close_window(focus_state, index); - } - } - } - - #[profile] - pub fn set_mouse_cursor_state(&mut self, state: MouseCursorState, client_tick: ClientTick) { - self.mouse_cursor.set_state(state, client_tick) - } - - #[profile("get first focused element")] - pub fn first_focused_element(&self, focus_state: &mut FocusState) { - if self.windows.is_empty() { - return; - } - - let window_index = self.windows.len() - 1; - let element = self.windows.last().unwrap().0.first_focused_element(); - - focus_state.set_focused_element(element, window_index); - } - - #[profile] - pub fn restore_focus(&self, focus_state: &mut FocusState) { - if self.windows.is_empty() { - return; - } - - let window_index = self.windows.len() - 1; - let element = self.windows.last().unwrap().0.restore_focus(); - - focus_state.set_focused_element(element, window_index); - } - - pub fn hide_mouse_cursor(&mut self) { - self.mouse_cursor_hidden = true; - } - - pub fn show_mouse_cursor(&mut self) { - self.mouse_cursor_hidden = false; - } -} diff --git a/src/interface/provider.rs b/src/interface/provider.rs deleted file mode 100644 index 76bef791..00000000 --- a/src/interface/provider.rs +++ /dev/null @@ -1,12 +0,0 @@ -use derive_new::new; - -use crate::graphics::GraphicsSettings; -#[cfg(feature = "debug")] -use crate::graphics::RenderSettings; - -#[derive(new)] -pub struct StateProvider<'t> { - pub graphics_settings: &'t GraphicsSettings, - #[cfg(feature = "debug")] - pub render_settings: &'t RenderSettings, -} diff --git a/src/interface/settings.rs b/src/interface/settings.rs deleted file mode 100644 index 3d33a8a7..00000000 --- a/src/interface/settings.rs +++ /dev/null @@ -1,118 +0,0 @@ -use procedural::{dimension_bound, PrototypeElement}; -use ron::ser::PrettyConfig; -use serde::{Deserialize, Serialize}; -use walkdir::WalkDir; - -#[cfg(feature = "debug")] -use crate::debug::*; -use crate::interface::*; - -#[derive(Serialize, Deserialize)] -#[serde(transparent)] -pub struct ThemeSelector<const KIND: ThemeKind>(pub String); - -impl<const KIND: ThemeKind> ThemeSelector<KIND> { - pub fn get_file(&self) -> &str { - &self.0 - } - - pub fn set_file(&mut self, file: String) { - self.0 = file; - } -} - -impl<const KIND: ThemeKind> PrototypeElement for ThemeSelector<KIND> { - fn to_element(&self, display: String) -> ElementCell { - let state = TrackedState::new(self.0.clone()); - - let themes = WalkDir::new("client/themes/") - .into_iter() - .filter_map(|entry| entry.ok()) - .filter(|entry| entry.file_type().is_file()) - .filter_map(|path| { - let name = path.path().file_name()?.to_str()?.strip_suffix(".ron")?.to_owned(); - let file_path = format!("client/themes/{}.ron", name); - Some((name, file_path)) - }) - .collect(); - - let elements = vec![ - Text::default().with_text(display).with_width(dimension_bound!(50%)).wrap(), - PickList::default() - .with_options(themes) - .with_selected(state.clone()) - .with_event(Box::new(move || { - vec![ClickAction::Event(UserEvent::SetThemeFile { - theme_file: state.get(), - theme_kind: KIND, - })] - })) - .with_width(dimension_bound!(!)) - .wrap(), - ]; - - Container::new(elements).wrap() - } -} - -#[derive(Serialize, Deserialize, PrototypeElement)] -pub struct InterfaceSettings { - #[name("Scaling")] - pub scaling: MutableRange<f32, Resolve>, - #[name("Main theme")] - pub main_theme: ThemeSelector<{ ThemeKind::Main }>, - #[name("Menu theme")] - pub menu_theme: ThemeSelector<{ ThemeKind::Menu }>, - #[name("Game theme")] - pub game_theme: ThemeSelector<{ ThemeKind::Game }>, -} - -impl Default for InterfaceSettings { - fn default() -> Self { - let scaling = MutableRange::new(1.0, 0.5, 2.5); - let main_theme = ThemeSelector("client/themes/main.ron".to_string()); - let menu_theme = ThemeSelector("client/themes/menu.ron".to_string()); - let game_theme = ThemeSelector("client/themes/game.ron".to_string()); - - Self { - scaling, - main_theme, - menu_theme, - game_theme, - } - } -} - -impl InterfaceSettings { - pub fn new() -> Self { - Self::load().unwrap_or_else(|| { - #[cfg(feature = "debug")] - print_debug!("failed to load interface settings from {}filename{}", MAGENTA, NONE); - - Default::default() - }) - } - - pub fn load() -> Option<Self> { - #[cfg(feature = "debug")] - print_debug!("loading interface settings from {}filename{}", MAGENTA, NONE); - - std::fs::read_to_string("client/interface_settings.ron") - .ok() - .and_then(|data| ron::from_str(&data).ok()) - } - - pub fn save(&self) { - #[cfg(feature = "debug")] - print_debug!("saving interface settings to {}filename{}", MAGENTA, NONE); - - let data = ron::ser::to_string_pretty(self, PrettyConfig::new()).unwrap(); - std::fs::write("client/interface_settings.ron", data).expect("unable to write file"); - } -} - -impl Drop for InterfaceSettings { - fn drop(&mut self) { - self.save(); - } -} diff --git a/src/interface/state.rs b/src/interface/state.rs deleted file mode 100644 index 6d72a552..00000000 --- a/src/interface/state.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::cell::{Ref, RefCell}; -use std::ops::Not; -use std::rc::Rc; - -use super::{ClickAction, StateProvider}; - -/// The state of a value borrowed by [`borrow_mut`](TrackedState::with_mut). -pub enum ValueState<T> { - Mutated(T), - Unchanged(T), -} - -#[derive(Default)] -struct InnerState<VALUE> { - value: VALUE, - version: usize, -} - -impl<VALUE> InnerState<VALUE> { - pub fn new(value: VALUE) -> Self { - Self { value, version: 0 } - } - - pub fn bump_version(&mut self) { - self.version = self.version.wrapping_add(1); - } -} - -#[derive(Default)] -pub struct TrackedState<VALUE>(Rc<RefCell<InnerState<VALUE>>>); - -impl<VALUE> TrackedState<VALUE> { - pub fn new(value: VALUE) -> TrackedState<VALUE> { - Self(Rc::new(RefCell::new(InnerState::new(value)))) - } - - pub fn set(&mut self, data: VALUE) { - let mut inner = self.0.borrow_mut(); - inner.value = data; - inner.bump_version(); - } - - pub fn borrow(&self) -> Ref<'_, VALUE> { - Ref::map(self.0.borrow(), |inner| &inner.value) - } - - pub fn get_version(&self) -> usize { - self.0.borrow().version - } - - /// Work on a mutable reference of the inner value. The provided closure has - /// to report back whether or not the value inside was mutated. If any state - /// change occurred, the closure *must* return [`ValueState::Mutated`]. - /// - /// NOTE: It is imperative that the correct state is - /// returned. Returning [`ValueState::Mutated`] when no modification was - /// done might result in unnecessary updates. Similarly, returning - /// [`ValueState::Unchanged`] when the value was mutated will result in - /// no update at all. - pub fn with_mut<CLOSURE, RETURN>(&mut self, closure: CLOSURE) -> RETURN - where - CLOSURE: FnOnce(&mut VALUE) -> ValueState<RETURN>, - { - let mut inner = self.0.borrow_mut(); - - match closure(&mut inner.value) { - ValueState::Mutated(return_value) => { - inner.bump_version(); - return_value - } - ValueState::Unchanged(return_value) => return_value, - } - } - - pub fn update(&mut self) { - self.0.borrow_mut().bump_version(); - } - - pub fn new_remote(&self) -> Remote<VALUE> { - let tracked_state = self.clone(); - let version = self.get_version(); - - Remote { tracked_state, version } - } -} - -impl<VALUE> TrackedState<VALUE> -where - VALUE: Clone, -{ - pub fn get(&self) -> VALUE { - self.0.borrow().value.clone() - } -} - -impl<VALUE> TrackedState<Vec<VALUE>> { - pub fn clear(&mut self) { - let mut inner = self.0.borrow_mut(); - inner.value.clear(); - inner.bump_version(); - } - - pub fn push(&mut self, item: VALUE) { - let mut inner = self.0.borrow_mut(); - inner.value.push(item); - inner.bump_version(); - } - - pub fn retain<F>(&mut self, mut f: F) - where - F: FnMut(&VALUE) -> bool, - { - let mut inner = self.0.borrow_mut(); - let previous_length = inner.value.len(); - - inner.value.retain_mut(|element| f(element)); - - let new_length = inner.value.len(); - let has_updated = new_length < previous_length; - - if has_updated { - inner.bump_version(); - } - } - - pub fn len(&self) -> usize { - self.borrow().len() - } -} - -pub trait TrackedStateTake<VALUE> { - fn take(&mut self) -> VALUE; -} - -impl<VALUE> TrackedStateTake<VALUE> for TrackedState<VALUE> -where - VALUE: Default, -{ - default fn take(&mut self) -> VALUE { - let mut taken_value = VALUE::default(); - let inner_value = &mut self.0.borrow_mut().value; - std::mem::swap(&mut taken_value, inner_value); - - taken_value - } -} - -impl<VALUE> TrackedStateTake<Option<VALUE>> for TrackedState<Option<VALUE>> -where - VALUE: Default, -{ - fn take(&mut self) -> Option<VALUE> { - let option = self.0.borrow_mut().value.take(); - - // NOTE: Unnecessary updates might have huge impacts on performance, so we try - // to only update if we actually took a value. - if option.is_some() { - self.update(); - } - - option - } -} - -impl<VALUE> TrackedState<VALUE> -where - VALUE: Not<Output = VALUE> + Copy, -{ - pub fn toggle(&mut self) { - let mut inner = self.0.borrow_mut(); - inner.value = !inner.value; - inner.bump_version(); - } - - pub fn toggle_action(&self) -> Box<impl FnMut() -> Vec<ClickAction>> { - let mut cloned = self.clone(); - Box::new(move || { - cloned.toggle(); - Vec::new() - }) - } -} - -impl TrackedState<bool> { - pub fn selector(&self) -> impl Fn(&StateProvider) -> bool { - let cloned = self.clone(); - move |_: &StateProvider| cloned.get() - } -} - -impl<VALUE> Clone for TrackedState<VALUE> { - fn clone(&self) -> Self { - Self(Rc::clone(&self.0)) - } -} - -pub struct Remote<VALUE> { - tracked_state: TrackedState<VALUE>, - version: usize, -} - -impl<VALUE> Remote<VALUE> { - pub fn new(value: VALUE) -> Remote<VALUE> { - TrackedState::new(value).new_remote() - } - - pub fn clone_state(&self) -> TrackedState<VALUE> { - self.tracked_state.clone() - } - - pub fn borrow(&self) -> Ref<'_, VALUE> { - self.tracked_state.borrow() - } - - pub fn consume_changed(&mut self) -> bool { - let version = self.tracked_state.get_version(); - let changed = self.version != version; - self.version = version; - - changed - } -} - -impl<VALUE> Remote<VALUE> -where - VALUE: Clone, -{ - pub fn get(&self) -> VALUE { - self.tracked_state.get() - } -} - -impl<VALUE> Clone for Remote<VALUE> { - fn clone(&self) -> Self { - let tracked_state = self.tracked_state.clone(); - let version = self.version; - - Self { tracked_state, version } - } -} diff --git a/src/interface/windows/account/login.rs b/src/interface/windows/account/login.rs deleted file mode 100644 index aba97ea5..00000000 --- a/src/interface/windows/account/login.rs +++ /dev/null @@ -1,269 +0,0 @@ -use std::cell::RefCell; -use std::ops::Not; -use std::rc::Rc; - -use derive_new::new; - -use crate::input::UserEvent; -use crate::interface::state::ValueState; -use crate::interface::*; -use crate::loaders::ClientInfo; -use crate::network::LoginSettings; - -#[derive(new)] -pub struct LoginWindow<'a> { - client_info: &'a ClientInfo, -} - -impl<'a> LoginWindow<'a> { - pub const WINDOW_CLASS: &'static str = "login"; -} - -impl<'a> PrototypeWindow for LoginWindow<'a> { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let mut login_settings = LoginSettings::new(); - - let options = self - .client_info - .services - .iter() - .map(|service| (service.display_name.clone().unwrap(), service.service_id())) - .collect(); - - // FIX: This will panic when no services are present. What is the correct - // behavior? - let selected_service = login_settings - .recent_service_id - // Only use the recent server if it is still in the client info - .filter(|&recent_service_id| { - self.client_info - .services - .iter() - .any(|service| service.service_id() == recent_service_id) - }) - .unwrap_or_else(|| self.client_info.services[0].service_id()); - - let saved_settings = login_settings.service_settings.entry(selected_service).or_default(); - - let username = TrackedState::new(saved_settings.username.clone()); - let password = TrackedState::new(saved_settings.password.clone()); - - let selected_service = TrackedState::new(selected_service); - let login_settings = Rc::new(RefCell::new(login_settings)); - - let selector = { - let username = username.clone(); - let password = password.clone(); - move || !username.borrow().is_empty() && !password.borrow().is_empty() - }; - - let service_changed = { - let mut username = username.clone(); - let mut password = password.clone(); - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - Box::new(move || { - let service_id = selected_service.get(); - let login_settings = login_settings.borrow_mut(); - - if let Some(saved_settings) = login_settings.service_settings.get(&service_id) { - username.with_mut(|username| { - *username = saved_settings.username.clone(); - ValueState::Mutated(()) - }); - password.with_mut(|password| { - *password = saved_settings.password.clone(); - ValueState::Mutated(()) - }); - } - - Vec::new() - }) - }; - - let login_action = { - let username = username.clone(); - let password = password.clone(); - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - move || { - // TODO: Deduplicate code - let service_id = selected_service.get(); - - let mut login_settings = login_settings.borrow_mut(); - login_settings.recent_service_id = Some(service_id); - - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - saved_settings.username = username.borrow().clone(); - saved_settings.password = password.borrow().clone(); - - vec![ClickAction::Event(UserEvent::LogIn { - service_id: selected_service.get(), - username: username.borrow().clone(), - password: password.borrow().clone(), - })] - } - }; - - let username_action = { - let username = username.clone(); - Box::new(move || { - username - .borrow() - .is_empty() - .not() - .then_some(vec![ClickAction::FocusNext(FocusMode::FocusNext)]) - .unwrap_or_default() - }) - }; - - let password_action = { - let username = username.clone(); - let password = password.clone(); - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - Box::new(move || match password.borrow().is_empty() { - _ if username.borrow().is_empty() => vec![ClickAction::FocusNext(FocusMode::FocusPrevious)], - true => Vec::new(), - false => { - // TODO: Deduplicate code - let service_id = selected_service.get(); - - let mut login_settings = login_settings.borrow_mut(); - login_settings.recent_service_id = Some(service_id); - - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - saved_settings.username = username.borrow().clone(); - saved_settings.password = password.borrow().clone(); - - vec![ClickAction::Event(UserEvent::LogIn { - service_id: selected_service.get(), - username: username.borrow().clone(), - password: password.borrow().clone(), - })] - } - }) - }; - - let remember_username_selector = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - move |_: &StateProvider| { - let service_id = selected_service.get(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - - saved_settings.remember_username - } - }; - - let remember_password_selector = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - move |_: &StateProvider| { - let service_id = selected_service.get(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - - saved_settings.remember_password - } - }; - - let remember_username_action = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - Box::new(move || { - let service_id = selected_service.get(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - - saved_settings.remember_username = !saved_settings.remember_username; - - Vec::new() - }) - }; - - let remember_password_action = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - Box::new(move || { - let service_id = selected_service.get(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - - saved_settings.remember_password = !saved_settings.remember_password; - - Vec::new() - }) - }; - - let elements = vec![ - Text::default().with_text("Select service").wrap(), - PickList::default() - .with_options(options) - .with_selected(selected_service) - .with_event(service_changed) - .wrap(), - Text::default().with_text("Account data").wrap(), - InputFieldBuilder::new() - .with_state(username) - .with_ghost_text("Username") - .with_enter_action(username_action) - .with_length(24) - .build() - .wrap(), - InputFieldBuilder::new() - .with_state(password) - .with_ghost_text("Password") - .with_enter_action(password_action) - .with_length(24) - .hidden() - .build() - .wrap(), - Container::new({ - vec![ - StateButtonBuilder::new() - .with_text("Remember username") - .with_selector(remember_username_selector) - .with_event(remember_username_action) - .with_transparent_background() - .build() - .wrap(), - StateButtonBuilder::new() - .with_text("Remember password") - .with_selector(remember_password_selector) - .with_event(remember_password_action) - .with_transparent_background() - .build() - .wrap(), - ] - }) - .wrap(), - ButtonBuilder::new() - .with_text("Log in") - .with_disabled_selector(selector) - .with_event(Box::new(login_action)) - .build() - .wrap(), - ]; - - WindowBuilder::new() - .with_title("Log In".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .with_theme_kind(ThemeKind::Menu) - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/character/equipment.rs b/src/interface/windows/character/equipment.rs deleted file mode 100644 index 83d62a97..00000000 --- a/src/interface/windows/character/equipment.rs +++ /dev/null @@ -1,31 +0,0 @@ -use procedural::size_bound; - -use crate::interface::*; -use crate::inventory::Item; - -#[derive(new)] -pub struct EquipmentWindow { - items: Remote<Vec<Item>>, -} - -impl EquipmentWindow { - pub const WINDOW_CLASS: &'static str = "equipment"; -} - -impl PrototypeWindow for EquipmentWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![EquipmentContainer::new(self.items.clone()).wrap()]; - - WindowBuilder::new() - .with_title("Equipment".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(size_bound!(150 > 200 < 300, ?)) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/character/hotbar.rs b/src/interface/windows/character/hotbar.rs deleted file mode 100644 index 312df286..00000000 --- a/src/interface/windows/character/hotbar.rs +++ /dev/null @@ -1,30 +0,0 @@ -use procedural::size_bound; - -use crate::interface::*; -use crate::inventory::Skill; - -#[derive(new)] -pub struct HotbarWindow { - skills: Remote<[Option<Skill>; 10]>, -} - -impl HotbarWindow { - pub const WINDOW_CLASS: &'static str = "hotbar"; -} - -impl PrototypeWindow for HotbarWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![HotbarContainer::new(self.skills.clone()).wrap()]; - - WindowBuilder::new() - .with_title("Hotbar".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(size_bound!(300 > 400 < 500, ?)) - .with_elements(elements) - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/character/inventory.rs b/src/interface/windows/character/inventory.rs deleted file mode 100644 index 9f1cb334..00000000 --- a/src/interface/windows/character/inventory.rs +++ /dev/null @@ -1,31 +0,0 @@ -use procedural::size_bound; - -use crate::interface::*; -use crate::inventory::Item; - -#[derive(new)] -pub struct InventoryWindow { - items: Remote<Vec<Item>>, -} - -impl InventoryWindow { - pub const WINDOW_CLASS: &'static str = "inventory"; -} - -impl PrototypeWindow for InventoryWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![InventoryContainer::new(self.items.clone()).wrap()]; - - WindowBuilder::new() - .with_title("Inventory".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(size_bound!(300 > 400 < 500, ? < 80%)) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/character/selection.rs b/src/interface/windows/character/selection.rs deleted file mode 100644 index bb209dc5..00000000 --- a/src/interface/windows/character/selection.rs +++ /dev/null @@ -1,36 +0,0 @@ -use derive_new::new; -use procedural::size_bound; - -use crate::interface::*; -use crate::network::CharacterInformation; - -#[derive(new)] -pub struct CharacterSelectionWindow { - characters: Remote<Vec<CharacterInformation>>, - move_request: Remote<Option<usize>>, - slot_count: usize, -} - -impl CharacterSelectionWindow { - pub const WINDOW_CLASS: &'static str = "character_selection"; -} - -impl PrototypeWindow for CharacterSelectionWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = (0..self.slot_count) - .map(|slot| CharacterPreview::new(self.characters.clone(), self.move_request.clone(), slot).wrap()) - .collect(); - - WindowBuilder::new() - .with_title("Character Selection".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(size_bound!(400 > 700 < 1000, ?)) - .with_elements(elements) - .with_theme_kind(ThemeKind::Menu) - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/character/skill_tree.rs b/src/interface/windows/character/skill_tree.rs deleted file mode 100644 index d6e47bdf..00000000 --- a/src/interface/windows/character/skill_tree.rs +++ /dev/null @@ -1,31 +0,0 @@ -use procedural::size_bound; - -use crate::interface::*; -use crate::inventory::Skill; - -#[derive(new)] -pub struct SkillTreeWindow { - skills: Remote<Vec<Skill>>, -} - -impl SkillTreeWindow { - pub const WINDOW_CLASS: &'static str = "skill_tree"; -} - -impl PrototypeWindow for SkillTreeWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![SkillTreeContainer::new(self.skills.clone()).wrap()]; - - WindowBuilder::new() - .with_title("Skill tree".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(size_bound!(300 > 400 < 500, ? < 80%)) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/debug/inspector.rs b/src/interface/windows/debug/inspector.rs deleted file mode 100644 index 0c664bf4..00000000 --- a/src/interface/windows/debug/inspector.rs +++ /dev/null @@ -1,27 +0,0 @@ -use procedural::size_bound; - -use crate::debug::Measurement; -use crate::interface::*; - -pub struct FrameInspectorWindow { - measurement: Measurement, -} - -impl FrameInspectorWindow { - pub fn new(measurement: Measurement) -> Self { - Self { measurement } - } -} - -impl PrototypeWindow for FrameInspectorWindow { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![FrameInspectorView::new(self.measurement.clone()).wrap()]; - - WindowBuilder::new() - .with_title("Frame Inspector".to_string()) - .with_size_bound(size_bound!(200 > 500 < 900, ?)) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/debug/profiler.rs b/src/interface/windows/debug/profiler.rs deleted file mode 100644 index f7684c4b..00000000 --- a/src/interface/windows/debug/profiler.rs +++ /dev/null @@ -1,73 +0,0 @@ -use procedural::{dimension_bound, size_bound}; - -use crate::interface::*; - -pub struct ProfilerWindow { - always_update: TrackedState<bool>, - visible_thread: TrackedState<ProfilerThread>, -} - -impl ProfilerWindow { - pub const WINDOW_CLASS: &'static str = "profiler"; - - pub fn new() -> Self { - Self { - always_update: TrackedState::new(true), - visible_thread: TrackedState::new(ProfilerThread::Main), - } - } -} - -impl PrototypeWindow for ProfilerWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let toggle_halting = || { - let is_profiler_halted = is_profiler_halted(); - set_profiler_halted(!is_profiler_halted); - Vec::new() - }; - - let elements = vec![ - PickList::default() - .with_options(vec![ - ("Main thread", ProfilerThread::Main), - ("Picker thread", ProfilerThread::Picker), - ("Shadow thread", ProfilerThread::Shadow), - ("Deferred thread", ProfilerThread::Deferred), - ]) - .with_selected(self.visible_thread.clone()) - .with_width(dimension_bound!(150)) - .with_event(Box::new(Vec::new)) - .wrap(), - StateButtonBuilder::new() - .with_text("Always update") - .with_selector(self.always_update.selector()) - .with_event(self.always_update.toggle_action()) - .with_width_bound(dimension_bound!(150)) - .build() - .wrap(), - StateButtonBuilder::new() - .with_text("Halt") - .with_selector(|_: &StateProvider| is_profiler_halted()) - .with_event(Box::new(toggle_halting)) - .with_width_bound(dimension_bound!(150)) - .build() - .wrap(), - ElementWrap::wrap(FrameView::new( - self.always_update.new_remote(), - self.visible_thread.new_remote(), - )), - ]; - - WindowBuilder::new() - .with_title("Profiler".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(size_bound!(200 > 500 < 900, ?)) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/generic/dialog.rs b/src/interface/windows/generic/dialog.rs deleted file mode 100644 index 946216b8..00000000 --- a/src/interface/windows/generic/dialog.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::interface::*; - -pub struct DialogWindow { - elements: TrackedState<Vec<DialogElement>>, - npc_id: EntityId, -} - -impl DialogWindow { - pub const WINDOW_CLASS: &'static str = "dialog"; - - pub fn new(text: String, npc_id: EntityId) -> (Self, TrackedState<Vec<DialogElement>>) { - let elements = TrackedState::new(vec![DialogElement::Text(text)]); - - let dialog_window = Self { - elements: elements.clone(), - npc_id, - }; - - (dialog_window, elements) - } -} - -impl PrototypeWindow for DialogWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![DialogContainer::new(self.elements.new_remote(), self.npc_id).wrap()]; - - WindowBuilder::new() - .with_title("Dialog".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/generic/error.rs b/src/interface/windows/generic/error.rs deleted file mode 100644 index 2f01d65d..00000000 --- a/src/interface/windows/generic/error.rs +++ /dev/null @@ -1,29 +0,0 @@ -use derive_new::new; -use procedural::size_bound; - -use crate::graphics::Color; -use crate::interface::*; - -#[derive(new)] -pub struct ErrorWindow { - message: String, -} - -impl PrototypeWindow for ErrorWindow { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![ - Text::default() - .with_text(self.message.clone()) - .with_foreground_color(|_| Color::rgb_u8(220, 100, 100)) - .wrap(), - ]; - - WindowBuilder::new() - .with_title("Error".to_string()) - .with_size_bound(size_bound!(300 > 400 < 500, ?)) - .with_elements(elements) - .closable() - .with_theme_kind(ThemeKind::Menu) - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/mod.rs b/src/interface/windows/mod.rs deleted file mode 100644 index c65d69e0..00000000 --- a/src/interface/windows/mod.rs +++ /dev/null @@ -1,303 +0,0 @@ -mod account; -mod builder; -mod cache; -mod character; -#[cfg(feature = "debug")] -mod debug; -mod friends; -mod generic; -mod mutable; -mod prototype; -mod settings; - -use procedural::size_bound; - -pub use self::account::*; -pub use self::builder::WindowBuilder; -pub use self::cache::*; -pub use self::character::*; -#[cfg(feature = "debug")] -pub use self::debug::*; -pub use self::friends::*; -pub use self::generic::*; -pub use self::mutable::*; -pub use self::prototype::PrototypeWindow; -pub use self::settings::*; -use crate::graphics::{InterfaceRenderer, Renderer}; -use crate::input::MouseInputMode; -use crate::interface::*; -use crate::loaders::FontLoader; - -pub struct Window { - window_class: Option<String>, - position: ScreenPosition, - size_bound: SizeBound, - size: ScreenSize, - elements: Vec<ElementCell>, - popup_element: Option<(ElementCell, Tracker<ScreenPosition>, Tracker<ScreenSize>)>, - closable: bool, - background_color: Option<ColorSelector>, - theme_kind: ThemeKind, -} - -impl Window { - pub fn get_window_class(&self) -> Option<&str> { - self.window_class.as_deref() - } - - fn get_background_color(&self, theme: &InterfaceTheme) -> Color { - self.background_color - .as_ref() - .map(|closure| closure(theme)) - .unwrap_or(theme.window.background_color.get()) - } - - pub fn has_transparency(&self, theme: &InterfaceTheme) -> bool { - const TRANSPARENCY_THRESHOLD: f32 = 0.999; - self.get_background_color(theme).alpha < TRANSPARENCY_THRESHOLD - } - - pub fn is_closable(&self) -> bool { - self.closable - } - - pub fn get_theme_kind(&self) -> ThemeKind { - self.theme_kind - } - - pub fn resolve( - &mut self, - font_loader: Rc<RefCell<FontLoader>>, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - available_space: ScreenSize, - ) -> (Option<&str>, ScreenPosition, ScreenSize) { - let mut placement_resolver = PlacementResolver::new( - font_loader.clone(), - available_space, - self.size, - &self.size_bound, - theme.window.border_size.get(), - theme.window.gaps.get(), - interface_settings.scaling.get(), - ); - - self.elements - .iter() - .for_each(|element| element.borrow_mut().resolve(&mut placement_resolver, interface_settings, theme)); - - if self.size_bound.height.is_flexible() { - let parent_limits = placement_resolver.get_parent_limits(); - let final_height = theme.window.border_size.get().height + placement_resolver.final_height(); - - let final_height = self.size_bound.validated_height( - final_height, - available_space.height.into(), - available_space.height.into(), - &parent_limits, - interface_settings.scaling.get(), - ); - - self.size.height = final_height; - self.validate_size(interface_settings, available_space); - } - - self.validate_position(available_space); - - if let Some((popup, _, size_tracker)) = &self.popup_element { - let size = size_tracker().unwrap(); // FIX: Don't unwrap obviously - - let mut placement_resolver = PlacementResolver::new( - font_loader, - available_space, - // TODO: 250 is an arbitrary limitation. This should be replaced with a value based - // on some reasoning. - ScreenSize { - width: size.width, - height: 250.0, - }, - &size_bound!(100%, 0 > ? < 250), - ScreenSize::default(), //theme.window.border_size.get(), // TODO: Popup - ScreenSize::default(), //theme.window.gaps.get(), // TODO: Popup - interface_settings.scaling.get(), - ); - - popup.borrow_mut().resolve(&mut placement_resolver, interface_settings, theme); - }; - - (self.window_class.as_deref(), self.position, self.size) - } - - pub fn update(&mut self) -> Option<ChangeEvent> { - self.elements - .iter_mut() - .map(|element| element.borrow_mut().update()) - .fold(None, |current, other| { - current.zip_with(other, ChangeEvent::union).or(current).or(other) - }) - } - - pub fn first_focused_element(&self) -> Option<ElementCell> { - let element_cell = self.elements[0].clone(); - self.elements[0].borrow().focus_next(element_cell, None, Focus::downwards()) - } - - pub fn restore_focus(&self) -> Option<ElementCell> { - self.elements[0].borrow().restore_focus(self.elements[0].clone()) - } - - pub fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation { - let absolute_position = ScreenPosition::from_size(mouse_position - self.position); - - if let Some((popup, position_tracker, _)) = &self.popup_element { - let position = position_tracker().unwrap(); // FIX: Don't unwrap obviously - let position = ScreenPosition::from_size(mouse_position - position); - - match popup.borrow().hovered_element(position, mouse_mode) { - HoverInformation::Hovered => return HoverInformation::Element(popup.clone()), - HoverInformation::Missed => {} - hover_information => return hover_information, - } - } - - if absolute_position.left >= 0.0 - && absolute_position.top >= 0.0 - && absolute_position.left <= self.size.width - && absolute_position.top <= self.size.height - { - for element in &self.elements { - match element.borrow().hovered_element(absolute_position, mouse_mode) { - HoverInformation::Hovered => return HoverInformation::Element(element.clone()), - HoverInformation::Missed => {} - hover_information => return hover_information, - } - } - - return HoverInformation::Hovered; - } - - HoverInformation::Missed - } - - pub fn get_area(&self) -> (ScreenPosition, ScreenSize) { - (self.position, self.size) - } - - pub fn hovers_area(&self, position: ScreenPosition, size: ScreenSize) -> bool { - let self_combined = self.position + self.size; - let area_combined = position + size; - - self_combined.left > position.left - && self.position.left < area_combined.left - && self_combined.top > position.top - && self.position.top < area_combined.top - } - - pub fn offset(&mut self, available_space: ScreenSize, offset: ScreenPosition) -> Option<(&str, ScreenPosition)> { - self.position += offset; - self.validate_position(available_space); - self.window_class - .as_ref() - .map(|window_class| (window_class.as_str(), self.position)) - } - - fn validate_position(&mut self, available_space: ScreenSize) { - self.position = self.size_bound.validated_position(self.position, self.size, available_space); - } - - pub fn resize( - &mut self, - interface_settings: &InterfaceSettings, - _theme: &InterfaceTheme, - available_space: ScreenSize, - growth: ScreenSize, - ) -> (Option<&str>, ScreenSize) { - self.size += growth; - self.validate_size(interface_settings, available_space); - (self.window_class.as_deref(), self.size) - } - - fn validate_size(&mut self, interface_settings: &InterfaceSettings, available_space: ScreenSize) { - self.size = self - .size_bound - .validated_window_size(self.size, available_space, interface_settings.scaling.get()); - } - - pub fn open_popup(&mut self, element: ElementCell, position_tracker: Tracker<ScreenPosition>, size_tracker: Tracker<ScreenSize>) { - // NOTE: Very important to link back - let weak_element = Rc::downgrade(&element); - element.borrow_mut().link_back(weak_element, None); - - self.popup_element = Some((element, position_tracker, size_tracker)); - } - - pub fn close_popup(&mut self) { - self.popup_element = None; - } - - pub fn render( - &self, - render_target: &mut <InterfaceRenderer as Renderer>::Target, - renderer: &InterfaceRenderer, - state_provider: &StateProvider, - interface_settings: &InterfaceSettings, - theme: &InterfaceTheme, - hovered_element: Option<&dyn Element>, - focused_element: Option<&dyn Element>, - mouse_mode: &MouseInputMode, - ) { - let screen_clip = ScreenClip { - left: self.position.left, - top: self.position.top, - right: self.position.left + self.size.width, - bottom: self.position.top + self.size.height, - }; - - renderer.render_rectangle( - render_target, - self.position, - self.size, - screen_clip, - theme.window.corner_radius.get(), - self.get_background_color(theme), - ); - - self.elements.iter().for_each(|element| { - element.borrow().render( - render_target, - renderer, - state_provider, - interface_settings, - theme, - self.position, - screen_clip, - hovered_element, - focused_element, - mouse_mode, - false, - ) - }); - - if let Some((popup, position_tracker, _)) = &self.popup_element { - let position = position_tracker().unwrap(); // FIX: Don't unwrap obviously - - popup.borrow().render( - render_target, - renderer, - state_provider, - interface_settings, - theme, - position, - screen_clip, - hovered_element, - focused_element, - mouse_mode, - false, - ); - }; - } -} - -// Needed so that we can deallocate FramedWindow in another thread. -unsafe impl Send for Window {} -unsafe impl Sync for Window {} diff --git a/src/interface/windows/mutable/color.rs b/src/interface/windows/mutable/color.rs deleted file mode 100644 index 19232af2..00000000 --- a/src/interface/windows/mutable/color.rs +++ /dev/null @@ -1,35 +0,0 @@ -use derive_new::new; - -use crate::graphics::Color; -use crate::interface::*; - -#[derive(new)] -pub struct ColorWindow { - name: String, - reference: &'static Color, - change_event: Option<ChangeEvent>, -} - -impl PrototypeWindow for ColorWindow { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let rgb_elements = vec![ - Headline::new("red".to_string(), Headline::DEFAULT_SIZE).wrap(), - Slider::new(&self.reference.red, 0.0, 1.0, self.change_event).wrap(), - Headline::new("green".to_string(), Headline::DEFAULT_SIZE).wrap(), - Slider::new(&self.reference.green, 0.0, 1.0, self.change_event).wrap(), - Headline::new("blue".to_string(), Headline::DEFAULT_SIZE).wrap(), - Slider::new(&self.reference.blue, 0.0, 1.0, self.change_event).wrap(), - Headline::new("alpha".to_string(), Headline::DEFAULT_SIZE).wrap(), - Slider::new(&self.reference.alpha, 0.0, 1.0, self.change_event).wrap(), - ]; - - let elements = vec![Expandable::new("rgb".to_string(), rgb_elements, true).wrap()]; - - WindowBuilder::new() - .with_title(self.name.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/mutable/number.rs b/src/interface/windows/mutable/number.rs deleted file mode 100644 index 53361251..00000000 --- a/src/interface/windows/mutable/number.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::cmp::PartialOrd; - -use derive_new::new; -use num::traits::NumOps; -use num::{NumCast, Zero}; - -use crate::interface::*; - -#[derive(new)] -pub struct NumberWindow<T: 'static> { - name: String, - reference: &'static T, - minimum_value: T, - maximum_value: T, - change_event: Option<ChangeEvent>, -} - -impl<T: Zero + NumOps + NumCast + Copy + PartialOrd + 'static> PrototypeWindow for NumberWindow<T> { - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![ - Headline::new("value".to_string(), Headline::DEFAULT_SIZE).wrap(), - Slider::new(self.reference, self.minimum_value, self.maximum_value, self.change_event).wrap(), - ]; - - WindowBuilder::new() - .with_title(self.name.clone()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/prototype.rs b/src/interface/windows/prototype.rs deleted file mode 100644 index bdee874c..00000000 --- a/src/interface/windows/prototype.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::interface::{InterfaceSettings, ScreenSize, Window, WindowCache}; - -pub trait PrototypeWindow { - fn window_class(&self) -> Option<&str> { - None - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window; -} diff --git a/src/interface/windows/settings/audio.rs b/src/interface/windows/settings/audio.rs deleted file mode 100644 index 50004603..00000000 --- a/src/interface/windows/settings/audio.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::interface::{InterfaceSettings, PrototypeWindow, ScreenSize, SizeBound, Window, WindowBuilder, WindowCache}; - -#[derive(Default)] -pub struct AudioSettingsWindow; - -impl AudioSettingsWindow { - pub const WINDOW_CLASS: &'static str = "audio_settings"; -} - -impl PrototypeWindow for AudioSettingsWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![]; - - WindowBuilder::new() - .with_title("Audio Settings".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/settings/graphics.rs b/src/interface/windows/settings/graphics.rs deleted file mode 100644 index 1553a38a..00000000 --- a/src/interface/windows/settings/graphics.rs +++ /dev/null @@ -1,61 +0,0 @@ -use procedural::dimension_bound; - -use crate::graphics::{PresentModeInfo, ShadowDetail}; -use crate::input::UserEvent; -use crate::interface::*; - -#[derive(new)] -pub struct GraphicsSettingsWindow { - present_mode_info: PresentModeInfo, - shadow_detail: TrackedState<ShadowDetail>, -} - -impl GraphicsSettingsWindow { - pub const WINDOW_CLASS: &'static str = "graphics_settings"; -} - -impl PrototypeWindow for GraphicsSettingsWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let mut elements = vec![ - Text::default().with_text("Shadow detail").with_width(dimension_bound!(50%)).wrap(), - PickList::default() - .with_options(vec![ - ("Low", ShadowDetail::Low), - ("Medium", ShadowDetail::Medium), - ("High", ShadowDetail::High), - ("Ultra", ShadowDetail::Ultra), - ]) - .with_selected(self.shadow_detail.clone()) - .with_event(Box::new(Vec::new)) - .with_width(dimension_bound!(!)) - .wrap(), - interface_settings.to_element("Interface settings".to_string()), - ]; - - // TODO: Instead of not showing this option, disable the checkbox and add a - // tooltip - if self.present_mode_info.supports_immediate || self.present_mode_info.supports_mailbox { - elements.insert( - 0, - StateButtonBuilder::new() - .with_text("Framerate limit") - .with_selector(|state_provider| state_provider.graphics_settings.frame_limit) - .with_event(UserEvent::ToggleFrameLimit) - .build() - .wrap(), - ); - } - - WindowBuilder::new() - .with_title("Graphics Settings".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -} diff --git a/src/interface/windows/settings/render.rs b/src/interface/windows/settings/render.rs deleted file mode 100644 index f4ace21b..00000000 --- a/src/interface/windows/settings/render.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::input::UserEvent; -use crate::interface::*; - -macro render_state_button($display:expr, $event:expr, $selector:ident) { - StateButtonBuilder::new() - .with_text($display) - .with_selector(|state_provider| state_provider.render_settings.$selector) - .with_event($event) - .build() - .wrap() -} - -fn general_expandable() -> ElementCell { - let buttons = vec![ - render_state_button!("debug camera", UserEvent::ToggleUseDebugCamera, use_debug_camera), - render_state_button!("show fps", UserEvent::ToggleShowFramesPerSecond, show_frames_per_second), - render_state_button!("show wireframe", UserEvent::ToggleShowWireframe, show_wireframe), - render_state_button!("frustum culling", UserEvent::ToggleFrustumCulling, frustum_culling), - render_state_button!("show bounding boxes", UserEvent::ToggleShowBoundingBoxes, show_bounding_boxes), - ]; - - Expandable::new("general".to_string(), buttons, true).wrap() -} - -fn map_expandable() -> ElementCell { - let buttons = vec![ - render_state_button!("show map", UserEvent::ToggleShowMap, show_map), - render_state_button!("show objects", UserEvent::ToggleShowObjects, show_objects), - render_state_button!("show entities", UserEvent::ToggleShowEntities, show_entities), - render_state_button!("show water", UserEvent::ToggleShowWater, show_water), - render_state_button!("show indicators", UserEvent::ToggleShowIndicators, show_indicators), - ]; - - Expandable::new("map".to_string(), buttons, true).wrap() -} - -fn lighting_expandable() -> ElementCell { - let buttons = vec![ - render_state_button!("ambient light", UserEvent::ToggleShowAmbientLight, show_ambient_light), - render_state_button!( - "directional light", - UserEvent::ToggleShowDirectionalLight, - show_directional_light - ), - render_state_button!("point lights", UserEvent::ToggleShowPointLights, show_point_lights), - render_state_button!("particle lights", UserEvent::ToggleShowParticleLights, show_particle_lights), - ]; - - Expandable::new("lighting".to_string(), buttons, true).wrap() -} - -fn shadows_expandable() -> ElementCell { - let buttons = vec![render_state_button!( - "directional shadows", - UserEvent::ToggleShowDirectionalShadows, - show_directional_shadows - )]; - - Expandable::new("shadows".to_string(), buttons, true).wrap() -} - -fn markers_expandable() -> ElementCell { - let buttons = vec![ - render_state_button!("object markers", UserEvent::ToggleShowObjectMarkers, show_object_markers), - render_state_button!("light markers", UserEvent::ToggleShowLightMarkers, show_light_markers), - render_state_button!("sound markers", UserEvent::ToggleShowSoundMarkers, show_sound_markers), - render_state_button!("effect markers", UserEvent::ToggleShowEffectMarkers, show_effect_markers), - render_state_button!("particle markers", UserEvent::ToggleShowParticleMarkers, show_particle_markers), - render_state_button!("entity markers", UserEvent::ToggleShowEntityMarkers, show_entity_markers), - ]; - - Expandable::new("markers".to_string(), buttons, true).wrap() -} - -fn grid_expandable() -> ElementCell { - let buttons = vec![ - render_state_button!("map tiles", UserEvent::ToggleShowMapTiles, show_map_tiles), - render_state_button!("pathing", UserEvent::ToggleShowPathing, show_pathing), - ]; - - Expandable::new("grid".to_string(), buttons, true).wrap() -} - -fn buffers_expandable() -> ElementCell { - let buttons = vec![ - render_state_button!("diffuse buffer", UserEvent::ToggleShowDiffuseBuffer, show_diffuse_buffer), - render_state_button!("normal buffer", UserEvent::ToggleShowNormalBuffer, show_normal_buffer), - render_state_button!("water buffer", UserEvent::ToggleShowWaterBuffer, show_water_buffer), - render_state_button!("depth buffer", UserEvent::ToggleShowDepthBuffer, show_depth_buffer), - render_state_button!("shadow buffer", UserEvent::ToggleShowShadowBuffer, show_shadow_buffer), - render_state_button!("picker buffer", UserEvent::ToggleShowPickerBuffer, show_picker_buffer), - render_state_button!("font atlas", UserEvent::ToggleShowFontAtlas, show_font_atlas), - ]; - - Expandable::new("buffers".to_string(), buttons, true).wrap() -} - -#[derive(Default)] -pub struct RenderSettingsWindow; - -impl RenderSettingsWindow { - pub const WINDOW_CLASS: &'static str = "render_settings"; -} - -impl PrototypeWindow for RenderSettingsWindow { - fn window_class(&self) -> Option<&str> { - Self::WINDOW_CLASS.into() - } - - fn to_window(&self, window_cache: &WindowCache, interface_settings: &InterfaceSettings, available_space: ScreenSize) -> Window { - let elements = vec![ - general_expandable(), - map_expandable(), - lighting_expandable(), - shadows_expandable(), - markers_expandable(), - grid_expandable(), - buffers_expandable(), - ]; - - WindowBuilder::new() - .with_title("Render Settings".to_string()) - .with_class(Self::WINDOW_CLASS.to_string()) - .with_size_bound(SizeBound::DEFAULT_UNBOUNDED) - .with_elements(elements) - .closable() - .build(window_cache, interface_settings, available_space) - } -}