diff --git a/assets/kenny/arrowBeige_left.png b/assets/kenny/arrowBeige_left.png new file mode 100644 index 0000000000000000000000000000000000000000..0d2fdaae0a322aa0b01767f6838018fbe99f031b Binary files /dev/null and b/assets/kenny/arrowBeige_left.png differ diff --git a/assets/kenny/arrowBeige_right.png b/assets/kenny/arrowBeige_right.png new file mode 100644 index 0000000000000000000000000000000000000000..166bbfe83c11f9f8918309bd790fa0b8b1923ecd Binary files /dev/null and b/assets/kenny/arrowBeige_right.png differ diff --git a/assets/kenny/arrowBlue_left.png b/assets/kenny/arrowBlue_left.png new file mode 100644 index 0000000000000000000000000000000000000000..05d3b3b7bd788db6abf3351e1eba315c0f6f152b Binary files /dev/null and b/assets/kenny/arrowBlue_left.png differ diff --git a/assets/kenny/arrowBlue_right.png b/assets/kenny/arrowBlue_right.png new file mode 100644 index 0000000000000000000000000000000000000000..51fb299c4537f2a8b5a16ae0089ab0e41e0eecec Binary files /dev/null and b/assets/kenny/arrowBlue_right.png differ diff --git a/assets/kenny/arrowBrown_left.png b/assets/kenny/arrowBrown_left.png new file mode 100644 index 0000000000000000000000000000000000000000..de9961bc391b207b163a09827fd288127b163d14 Binary files /dev/null and b/assets/kenny/arrowBrown_left.png differ diff --git a/assets/kenny/arrowBrown_right.png b/assets/kenny/arrowBrown_right.png new file mode 100644 index 0000000000000000000000000000000000000000..2a6b571d6f308991f5822300c6f8ec770bbdba1e Binary files /dev/null and b/assets/kenny/arrowBrown_right.png differ diff --git a/assets/kenny/arrowSilver_left.png b/assets/kenny/arrowSilver_left.png new file mode 100644 index 0000000000000000000000000000000000000000..8ed60b705238c5782b255868c5198233edbda554 Binary files /dev/null and b/assets/kenny/arrowSilver_left.png differ diff --git a/assets/kenny/arrowSilver_right.png b/assets/kenny/arrowSilver_right.png new file mode 100644 index 0000000000000000000000000000000000000000..be71d37da86af30a30c960197227656aca4aee1b Binary files /dev/null and b/assets/kenny/arrowSilver_right.png differ diff --git a/assets/kenny/barBack_horizontalLeft.png b/assets/kenny/barBack_horizontalLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..801e6b5b29ba00f5009a9fcd462cfb4dabd77931 Binary files /dev/null and b/assets/kenny/barBack_horizontalLeft.png differ diff --git a/assets/kenny/barBack_horizontalMid.png b/assets/kenny/barBack_horizontalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..319bdbc38ebf193cee5310fdbb35ab589a5a3475 Binary files /dev/null and b/assets/kenny/barBack_horizontalMid.png differ diff --git a/assets/kenny/barBack_horizontalRight.png b/assets/kenny/barBack_horizontalRight.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a850f506334abd9d52f64f744c7387cab7a279 Binary files /dev/null and b/assets/kenny/barBack_horizontalRight.png differ diff --git a/assets/kenny/barBack_verticalBottom.png b/assets/kenny/barBack_verticalBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..f0e7064bd2886df713fc7e548b237c3021e6e259 Binary files /dev/null and b/assets/kenny/barBack_verticalBottom.png differ diff --git a/assets/kenny/barBack_verticalMid.png b/assets/kenny/barBack_verticalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3e8fd2b5e605adb97a592da9c0d528c352c1f9 Binary files /dev/null and b/assets/kenny/barBack_verticalMid.png differ diff --git a/assets/kenny/barBack_verticalTop.png b/assets/kenny/barBack_verticalTop.png new file mode 100644 index 0000000000000000000000000000000000000000..543cd193f4438d0971841dc245e78dda4b4de964 Binary files /dev/null and b/assets/kenny/barBack_verticalTop.png differ diff --git a/assets/kenny/barBlue_horizontalBlue.png b/assets/kenny/barBlue_horizontalBlue.png new file mode 100644 index 0000000000000000000000000000000000000000..5367dba38591073c177329b2d08825f0decf9eaf Binary files /dev/null and b/assets/kenny/barBlue_horizontalBlue.png differ diff --git a/assets/kenny/barBlue_horizontalLeft.png b/assets/kenny/barBlue_horizontalLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..0245eb3b533adb39089d3b5d5544f3040ce18938 Binary files /dev/null and b/assets/kenny/barBlue_horizontalLeft.png differ diff --git a/assets/kenny/barBlue_horizontalRight.png b/assets/kenny/barBlue_horizontalRight.png new file mode 100644 index 0000000000000000000000000000000000000000..e94c0c61e76ad123581c93972a91659a6ce7cec8 Binary files /dev/null and b/assets/kenny/barBlue_horizontalRight.png differ diff --git a/assets/kenny/barBlue_verticalBottom.png b/assets/kenny/barBlue_verticalBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..2834db913084b0aa6402b3e2b8db40d1b54771e8 Binary files /dev/null and b/assets/kenny/barBlue_verticalBottom.png differ diff --git a/assets/kenny/barBlue_verticalMid.png b/assets/kenny/barBlue_verticalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..7629e5527e7a9d9e2d430011c4ab7b143122e5d3 Binary files /dev/null and b/assets/kenny/barBlue_verticalMid.png differ diff --git a/assets/kenny/barBlue_verticalTop.png b/assets/kenny/barBlue_verticalTop.png new file mode 100644 index 0000000000000000000000000000000000000000..18dda588ea2c163cac750aae5d5b832ed7e33cc5 Binary files /dev/null and b/assets/kenny/barBlue_verticalTop.png differ diff --git a/assets/kenny/barGreen_horizontalLeft.png b/assets/kenny/barGreen_horizontalLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..5b08f21c6657ad089f2371a30c866dd0f7d2ca87 Binary files /dev/null and b/assets/kenny/barGreen_horizontalLeft.png differ diff --git a/assets/kenny/barGreen_horizontalMid.png b/assets/kenny/barGreen_horizontalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b5d775bebc60dc63879a9f100fa2dc4056366b Binary files /dev/null and b/assets/kenny/barGreen_horizontalMid.png differ diff --git a/assets/kenny/barGreen_horizontalRight.png b/assets/kenny/barGreen_horizontalRight.png new file mode 100644 index 0000000000000000000000000000000000000000..407b61b535d91dcbbd524f03d9f52bfd556bf039 Binary files /dev/null and b/assets/kenny/barGreen_horizontalRight.png differ diff --git a/assets/kenny/barGreen_verticalBottom.png b/assets/kenny/barGreen_verticalBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..1f733d4c07fa0bfad2d060ece7260ac0c573874c Binary files /dev/null and b/assets/kenny/barGreen_verticalBottom.png differ diff --git a/assets/kenny/barGreen_verticalMid.png b/assets/kenny/barGreen_verticalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..03ff210f290097aadfdadb8d958f16de269f4feb Binary files /dev/null and b/assets/kenny/barGreen_verticalMid.png differ diff --git a/assets/kenny/barGreen_verticalTop.png b/assets/kenny/barGreen_verticalTop.png new file mode 100644 index 0000000000000000000000000000000000000000..235afa0b4f1be3e64c4a4b26ed7471eb58e6d8cb Binary files /dev/null and b/assets/kenny/barGreen_verticalTop.png differ diff --git a/assets/kenny/barRed_horizontalLeft.png b/assets/kenny/barRed_horizontalLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8e7a6f84084924da9526600d46c5c8107a3313 Binary files /dev/null and b/assets/kenny/barRed_horizontalLeft.png differ diff --git a/assets/kenny/barRed_horizontalMid.png b/assets/kenny/barRed_horizontalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..99da8810a636da75e56777141a476a3349270a2c Binary files /dev/null and b/assets/kenny/barRed_horizontalMid.png differ diff --git a/assets/kenny/barRed_horizontalRight.png b/assets/kenny/barRed_horizontalRight.png new file mode 100644 index 0000000000000000000000000000000000000000..9ec2bd4fd1b9336a1ad147e72e0b90cd8a506335 Binary files /dev/null and b/assets/kenny/barRed_horizontalRight.png differ diff --git a/assets/kenny/barRed_verticalBottom.png b/assets/kenny/barRed_verticalBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..767d6a4c607e94fc625ef175be65ec02caefa3cf Binary files /dev/null and b/assets/kenny/barRed_verticalBottom.png differ diff --git a/assets/kenny/barRed_verticalMid.png b/assets/kenny/barRed_verticalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..934e6f80d48c32fd19067d255ded269a8d157e8c Binary files /dev/null and b/assets/kenny/barRed_verticalMid.png differ diff --git a/assets/kenny/barRed_verticalTop.png b/assets/kenny/barRed_verticalTop.png new file mode 100644 index 0000000000000000000000000000000000000000..35101937914a1de0c7b84c3fe246811b47fa7391 Binary files /dev/null and b/assets/kenny/barRed_verticalTop.png differ diff --git a/assets/kenny/barYellow_horizontalLeft.png b/assets/kenny/barYellow_horizontalLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..79c4b63fdde994900ebceb2c3eed8f52b9869908 Binary files /dev/null and b/assets/kenny/barYellow_horizontalLeft.png differ diff --git a/assets/kenny/barYellow_horizontalMid.png b/assets/kenny/barYellow_horizontalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..38f37b6c06e4a31b51fdea38752be70e9a77f953 Binary files /dev/null and b/assets/kenny/barYellow_horizontalMid.png differ diff --git a/assets/kenny/barYellow_horizontalRight.png b/assets/kenny/barYellow_horizontalRight.png new file mode 100644 index 0000000000000000000000000000000000000000..3d00f789fbbb163d20c5184a9970a02e6b218928 Binary files /dev/null and b/assets/kenny/barYellow_horizontalRight.png differ diff --git a/assets/kenny/barYellow_verticalBottom.png b/assets/kenny/barYellow_verticalBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..d0cc9d72dcdbe6c404639f66db905b3203fa29c0 Binary files /dev/null and b/assets/kenny/barYellow_verticalBottom.png differ diff --git a/assets/kenny/barYellow_verticalMid.png b/assets/kenny/barYellow_verticalMid.png new file mode 100644 index 0000000000000000000000000000000000000000..e2491258bddf717d2026d2f804bd89a935b4e4a4 Binary files /dev/null and b/assets/kenny/barYellow_verticalMid.png differ diff --git a/assets/kenny/barYellow_verticalTop.png b/assets/kenny/barYellow_verticalTop.png new file mode 100644 index 0000000000000000000000000000000000000000..18b96ab2111d5f88218092ca0d6e816552326977 Binary files /dev/null and b/assets/kenny/barYellow_verticalTop.png differ diff --git a/assets/kenny/buttonLong_beige.png b/assets/kenny/buttonLong_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b35372b38e503f3e22f3a05c529cd04d82991c Binary files /dev/null and b/assets/kenny/buttonLong_beige.png differ diff --git a/assets/kenny/buttonLong_beige_pressed.png b/assets/kenny/buttonLong_beige_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..25378784c18ce313bfe6ec505cef5d962f8d3778 Binary files /dev/null and b/assets/kenny/buttonLong_beige_pressed.png differ diff --git a/assets/kenny/buttonLong_blue.png b/assets/kenny/buttonLong_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..cbaf8d7817a9bb52324e9bdf9df811638a6a5bd8 Binary files /dev/null and b/assets/kenny/buttonLong_blue.png differ diff --git a/assets/kenny/buttonLong_blue_pressed.png b/assets/kenny/buttonLong_blue_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..8ad0b5c2c60076b7a300246003fb4a8fbd35500c Binary files /dev/null and b/assets/kenny/buttonLong_blue_pressed.png differ diff --git a/assets/kenny/buttonLong_brown.png b/assets/kenny/buttonLong_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..299dc59d80e91af63053fdc0fe26d6224e43fcfe Binary files /dev/null and b/assets/kenny/buttonLong_brown.png differ diff --git a/assets/kenny/buttonLong_brown_pressed.png b/assets/kenny/buttonLong_brown_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..ec0a49445c98f2f9dbc6285973a913eca9ed7be2 Binary files /dev/null and b/assets/kenny/buttonLong_brown_pressed.png differ diff --git a/assets/kenny/buttonLong_grey.png b/assets/kenny/buttonLong_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c3fe09dc4da9950b0d82d926402377ecee47d8 Binary files /dev/null and b/assets/kenny/buttonLong_grey.png differ diff --git a/assets/kenny/buttonLong_grey_pressed.png b/assets/kenny/buttonLong_grey_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..ef427446823017796b305b808a73e77fc5b9c149 Binary files /dev/null and b/assets/kenny/buttonLong_grey_pressed.png differ diff --git a/assets/kenny/buttonRound_beige.png b/assets/kenny/buttonRound_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..69cbb7ba27900d43ff74555b266d232c88f5c35c Binary files /dev/null and b/assets/kenny/buttonRound_beige.png differ diff --git a/assets/kenny/buttonRound_blue.png b/assets/kenny/buttonRound_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..f6fb596e506aaf4d41c5eeec08c5ac60cbad38e0 Binary files /dev/null and b/assets/kenny/buttonRound_blue.png differ diff --git a/assets/kenny/buttonRound_brown.png b/assets/kenny/buttonRound_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..cb824ea08112cade237ce70b0e480784d11b9f04 Binary files /dev/null and b/assets/kenny/buttonRound_brown.png differ diff --git a/assets/kenny/buttonRound_grey.png b/assets/kenny/buttonRound_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..1fe89f8acde8482d72d2a95572dbf13759eea766 Binary files /dev/null and b/assets/kenny/buttonRound_grey.png differ diff --git a/assets/kenny/buttonSquare_beige.png b/assets/kenny/buttonSquare_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..15ecc1cab174e47effe6b26d35594886c7b499e8 Binary files /dev/null and b/assets/kenny/buttonSquare_beige.png differ diff --git a/assets/kenny/buttonSquare_beige_pressed.png b/assets/kenny/buttonSquare_beige_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..2465c870a33cdc902374d3a2136f40c18ce2dcbd Binary files /dev/null and b/assets/kenny/buttonSquare_beige_pressed.png differ diff --git a/assets/kenny/buttonSquare_blue.png b/assets/kenny/buttonSquare_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..c165e51f7feb1169ed3cea51e4f87617a1e6b93c Binary files /dev/null and b/assets/kenny/buttonSquare_blue.png differ diff --git a/assets/kenny/buttonSquare_blue_pressed.png b/assets/kenny/buttonSquare_blue_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..8ab31c5c340034da8b1d95e05f9563cb20774ba3 Binary files /dev/null and b/assets/kenny/buttonSquare_blue_pressed.png differ diff --git a/assets/kenny/buttonSquare_brown.png b/assets/kenny/buttonSquare_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..1707aab0be8282fa1d7e1ed9e5f69f0170c21215 Binary files /dev/null and b/assets/kenny/buttonSquare_brown.png differ diff --git a/assets/kenny/buttonSquare_brown_pressed.png b/assets/kenny/buttonSquare_brown_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..0ea2427e98d65f0db66cd11011356c813c226945 Binary files /dev/null and b/assets/kenny/buttonSquare_brown_pressed.png differ diff --git a/assets/kenny/buttonSquare_grey.png b/assets/kenny/buttonSquare_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..f21b17e1b4b38c0bff17c3db279ec4b6a433ea8f Binary files /dev/null and b/assets/kenny/buttonSquare_grey.png differ diff --git a/assets/kenny/buttonSquare_grey_pressed.png b/assets/kenny/buttonSquare_grey_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..bf7b41072881803606334a14dae6e24b14145ae7 Binary files /dev/null and b/assets/kenny/buttonSquare_grey_pressed.png differ diff --git a/assets/kenny/cursorGauntlet_blue.png b/assets/kenny/cursorGauntlet_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9eaafe0d11b360e536799037004edab6b4dfbc Binary files /dev/null and b/assets/kenny/cursorGauntlet_blue.png differ diff --git a/assets/kenny/cursorGauntlet_bronze.png b/assets/kenny/cursorGauntlet_bronze.png new file mode 100644 index 0000000000000000000000000000000000000000..379b6f5c889eb8de8b7ad6738b7fd92144d26b33 Binary files /dev/null and b/assets/kenny/cursorGauntlet_bronze.png differ diff --git a/assets/kenny/cursorGauntlet_grey.png b/assets/kenny/cursorGauntlet_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..b6f84c34ad917931ce414044910c0f6693f9ae1a Binary files /dev/null and b/assets/kenny/cursorGauntlet_grey.png differ diff --git a/assets/kenny/cursorHand_beige.png b/assets/kenny/cursorHand_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..ff2bba7414d26868c7e3b9a1cf8e399ae0b653fb Binary files /dev/null and b/assets/kenny/cursorHand_beige.png differ diff --git a/assets/kenny/cursorHand_blue.png b/assets/kenny/cursorHand_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..1ee5baaf38d45a98f51b209a968502e46681c942 Binary files /dev/null and b/assets/kenny/cursorHand_blue.png differ diff --git a/assets/kenny/cursorHand_grey.png b/assets/kenny/cursorHand_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..fa83d37ef5c20e9bda94ed989ff8383a4139c8b8 Binary files /dev/null and b/assets/kenny/cursorHand_grey.png differ diff --git a/assets/kenny/cursorSword_bronze.png b/assets/kenny/cursorSword_bronze.png new file mode 100644 index 0000000000000000000000000000000000000000..2b43075a33e0f6bd05e39e834bc6ff5894bf76b8 Binary files /dev/null and b/assets/kenny/cursorSword_bronze.png differ diff --git a/assets/kenny/cursorSword_gold.png b/assets/kenny/cursorSword_gold.png new file mode 100644 index 0000000000000000000000000000000000000000..ccca5fd30a5ee35e6c5d59212e787285510d5033 Binary files /dev/null and b/assets/kenny/cursorSword_gold.png differ diff --git a/assets/kenny/cursorSword_silver.png b/assets/kenny/cursorSword_silver.png new file mode 100644 index 0000000000000000000000000000000000000000..1b37fe8e588fd429e789e50b9fd8b5675f48bbad Binary files /dev/null and b/assets/kenny/cursorSword_silver.png differ diff --git a/assets/kenny/iconCheck_beige.png b/assets/kenny/iconCheck_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..bc4790e7f1e91579f31fc16ca705dacd2719ec21 Binary files /dev/null and b/assets/kenny/iconCheck_beige.png differ diff --git a/assets/kenny/iconCheck_blue.png b/assets/kenny/iconCheck_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef72cde22fb2f178e6c6d14853473bd40039dcc Binary files /dev/null and b/assets/kenny/iconCheck_blue.png differ diff --git a/assets/kenny/iconCheck_bronze.png b/assets/kenny/iconCheck_bronze.png new file mode 100644 index 0000000000000000000000000000000000000000..cdaa2f239d8d53bcfdb88e300cde5de81ade3f9f Binary files /dev/null and b/assets/kenny/iconCheck_bronze.png differ diff --git a/assets/kenny/iconCheck_grey.png b/assets/kenny/iconCheck_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..e4016cf7d97d78ab3443a8b0df0f749b4311580e Binary files /dev/null and b/assets/kenny/iconCheck_grey.png differ diff --git a/assets/kenny/iconCircle_beige.png b/assets/kenny/iconCircle_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..a5e3ca1ed5671c2cfe54b60cc75e16902b6d5cd4 Binary files /dev/null and b/assets/kenny/iconCircle_beige.png differ diff --git a/assets/kenny/iconCircle_blue.png b/assets/kenny/iconCircle_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..7062e5c7d1efbb13987f28a76400c89fa8676546 Binary files /dev/null and b/assets/kenny/iconCircle_blue.png differ diff --git a/assets/kenny/iconCircle_brown.png b/assets/kenny/iconCircle_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..7abf40fe3ce29d58c2492c095da6c454a7b37e12 Binary files /dev/null and b/assets/kenny/iconCircle_brown.png differ diff --git a/assets/kenny/iconCircle_grey.png b/assets/kenny/iconCircle_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..d35d80be281cb016b4f8eeeeb381969fb7b0d67a Binary files /dev/null and b/assets/kenny/iconCircle_grey.png differ diff --git a/assets/kenny/iconCross_beige.png b/assets/kenny/iconCross_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..32278dccd955874b321a7410a35a84342a32065a Binary files /dev/null and b/assets/kenny/iconCross_beige.png differ diff --git a/assets/kenny/iconCross_blue.png b/assets/kenny/iconCross_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab70a18ce0590995d90340d15327f287575b306 Binary files /dev/null and b/assets/kenny/iconCross_blue.png differ diff --git a/assets/kenny/iconCross_brown.png b/assets/kenny/iconCross_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..7cce69ada1e4cb5a85cfbf0b4acd6ab9b6f1a5cd Binary files /dev/null and b/assets/kenny/iconCross_brown.png differ diff --git a/assets/kenny/iconCross_grey.png b/assets/kenny/iconCross_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..f57ccd8c322d5474a09f734c61f72fc63f65f442 Binary files /dev/null and b/assets/kenny/iconCross_grey.png differ diff --git a/assets/kenny/panelInset_beige.png b/assets/kenny/panelInset_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..f92416fa221ee96a706413b02140b7ecf08e46e1 Binary files /dev/null and b/assets/kenny/panelInset_beige.png differ diff --git a/assets/kenny/panelInset_beigeLight.png b/assets/kenny/panelInset_beigeLight.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed1a7bc90f65bce6867a26062079b362c4a3b6d Binary files /dev/null and b/assets/kenny/panelInset_beigeLight.png differ diff --git a/assets/kenny/panelInset_blue.png b/assets/kenny/panelInset_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..0c2b5851e38c010d067501f099cbf88ebc97a492 Binary files /dev/null and b/assets/kenny/panelInset_blue.png differ diff --git a/assets/kenny/panelInset_brown.png b/assets/kenny/panelInset_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..fc293f144b4acc58ac8c44ea2f7cb4ff532fc252 Binary files /dev/null and b/assets/kenny/panelInset_brown.png differ diff --git a/assets/kenny/panel_beige.png b/assets/kenny/panel_beige.png new file mode 100644 index 0000000000000000000000000000000000000000..a4a1d99d5249af3b6cb7e68681bb806e3b95d24b Binary files /dev/null and b/assets/kenny/panel_beige.png differ diff --git a/assets/kenny/panel_beigeLight.png b/assets/kenny/panel_beigeLight.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccf82cf70d500ed7d5e627f70b54e56cb02ed2a Binary files /dev/null and b/assets/kenny/panel_beigeLight.png differ diff --git a/assets/kenny/panel_blue.png b/assets/kenny/panel_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..cf58c662e703e08e83f31c56834224e6810ee1ac Binary files /dev/null and b/assets/kenny/panel_blue.png differ diff --git a/assets/kenny/panel_brown.png b/assets/kenny/panel_brown.png new file mode 100644 index 0000000000000000000000000000000000000000..97c381ba6000d74d29284c3e8bef0f9929c8ffdb Binary files /dev/null and b/assets/kenny/panel_brown.png differ diff --git a/assets/panel.png b/assets/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..739934c54b3e2103ddc44c3283f03c9dc08ac4a5 Binary files /dev/null and b/assets/panel.png differ diff --git a/bevy_kayak_ui/src/bevy_context.rs b/bevy_kayak_ui/src/bevy_context.rs index 0c2c974e74e5116e913d90fda7e2ad7c65e681c2..5eb9cfe732618bfef4450c4c14a44fb4a3a7e7a8 100644 --- a/bevy_kayak_ui/src/bevy_context.rs +++ b/bevy_kayak_ui/src/bevy_context.rs @@ -23,9 +23,6 @@ impl BevyContext { if let Ok(mut kayak_context) = kayak_context.write() { f(&mut app_styles, &mut kayak_context); - - kayak_context.render(); - kayak_context.widget_manager.dirty(true); } diff --git a/bevy_kayak_ui/src/lib.rs b/bevy_kayak_ui/src/lib.rs index fee1bbc002e4c64ee1a418d83d638763af7f2603..1f1d45ff7c38e3add19967a1d7e743095733ed21 100644 --- a/bevy_kayak_ui/src/lib.rs +++ b/bevy_kayak_ui/src/lib.rs @@ -1,7 +1,7 @@ use bevy::{ input::{mouse::MouseButtonInput, ElementState}, math::Vec2, - prelude::{EventReader, MouseButton, Plugin, Res, ResMut}, + prelude::{EventReader, IntoExclusiveSystem, MouseButton, Plugin, Res, World}, render2::color::Color, window::{CursorMoved, Windows}, }; @@ -13,6 +13,7 @@ mod render; pub use bevy_context::BevyContext; pub use camera::*; use kayak_core::InputEvent; +pub use render::unified::image::ImageManager; #[derive(Default)] pub struct BevyKayakUIPlugin; @@ -21,7 +22,8 @@ impl Plugin for BevyKayakUIPlugin { fn build(&self, app: &mut bevy::prelude::App) { app.add_plugin(render::BevyKayakUIRenderPlugin) .add_plugin(camera::KayakUICameraPlugin) - .add_system(update); + .add_system(process_events) + .add_system(update.exclusive_system()); } } @@ -29,11 +31,22 @@ pub(crate) fn to_bevy_color(color: &kayak_core::color::Color) -> Color { Color::rgba(color.r, color.g, color.b, color.a) } -pub fn update( - bevy_context: ResMut<BevyContext>, +pub fn update(world: &mut World) { + let bevy_context = world.remove_resource::<BevyContext>().unwrap(); + if let Ok(mut context) = bevy_context.kayak_context.write() { + context.set_global_state(std::mem::take(world)); + context.render(); + *world = context.take_global_state::<World>().unwrap() + } + + world.insert_resource(bevy_context); +} + +pub fn process_events( + bevy_context: Res<BevyContext>, + windows: Res<Windows>, mut cursor_moved_events: EventReader<CursorMoved>, mut mouse_button_input_events: EventReader<MouseButtonInput>, - windows: Res<Windows>, ) { let window_size = if let Some(window) = windows.get_primary() { Vec2::new(window.width(), window.height()) @@ -42,9 +55,8 @@ pub fn update( }; if let Ok(mut context) = bevy_context.kayak_context.write() { - context.render(); - let mut input_events = Vec::new(); + for event in cursor_moved_events.iter() { input_events.push(InputEvent::MouseMoved(( event.position.x as f32, diff --git a/bevy_kayak_ui/src/render/mod.rs b/bevy_kayak_ui/src/render/mod.rs index 6b41bf0457c2946305253858b7e5199855b0cba4..970ea9c859b141e90548d502c16562a44cb9a9d0 100644 --- a/bevy_kayak_ui/src/render/mod.rs +++ b/bevy_kayak_ui/src/render/mod.rs @@ -19,7 +19,7 @@ use self::ui_pass::TransparentUI; mod ui_pass; mod ui_pass_driver; -mod unified; +pub mod unified; pub mod node { pub const UI_PASS_DEPENDENCIES: &str = "ui_pass_dependencies"; diff --git a/bevy_kayak_ui/src/render/unified/font/extract.rs b/bevy_kayak_ui/src/render/unified/font/extract.rs index ce8844dde4d434a346ecd64e354e29836e7a6317..43bce7d403757fe05d37f183525f19f4003290b2 100644 --- a/bevy_kayak_ui/src/render/unified/font/extract.rs +++ b/bevy_kayak_ui/src/render/unified/font/extract.rs @@ -137,6 +137,9 @@ pub fn extract_texts( quad_type: UIQuadType::Text, type_index: 0, border_radius: (0.0, 0.0, 0.0, 0.0), + image: None, + uv_max: None, + uv_min: None, }, }); diff --git a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs index f45898b206eac724845a3ff152defc20f0c621b3..f9d526c3a8bacd28907f885bcaff608bb1831ae6 100644 --- a/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs +++ b/bevy_kayak_ui/src/render/unified/font/font_texture_cache.rs @@ -111,7 +111,7 @@ impl FontTextureCache { resource: BindingResource::Sampler(&gpu_image.sampler), }, ], - layout: &pipeline.image_layout, + layout: &pipeline.font_image_layout, }); self.bind_groups @@ -305,7 +305,6 @@ impl FontTextureCache { atlas_texture: &GpuImage, size: Vec2, ) { - dbg!(size); Self::create_texture( images, font_handle.clone_weak(), @@ -333,7 +332,7 @@ impl FontTextureCache { resource: BindingResource::Sampler(&gpu_image.sampler), }, ], - layout: &pipeline.image_layout, + layout: &pipeline.font_image_layout, }); bind_groups.insert(font_handle.clone_weak(), binding); diff --git a/bevy_kayak_ui/src/render/unified/font/mod.rs b/bevy_kayak_ui/src/render/unified/font/mod.rs index 1ae275c690267860df26b5dc60f641c700a4a052..8b2a9fff050a30a686c6dd3d4c92c376f2159046 100644 --- a/bevy_kayak_ui/src/render/unified/font/mod.rs +++ b/bevy_kayak_ui/src/render/unified/font/mod.rs @@ -90,14 +90,11 @@ pub fn set_font_texture( ) { // quick and dirty, run this for all textures anytime a texture is created. for event in texture_events.iter() { - dbg!(&event); match event { AssetEvent::Created { handle } => { let handle_path = asset_server.get_handle_path(handle).unwrap(); - dbg!(&handle_path); if handle_path.path().to_str().unwrap().contains("roboto") { if let Some(mut texture) = textures.get_mut(handle) { - dbg!("Setting font texture!"); texture.texture_descriptor.format = TextureFormat::Rgba8Unorm; texture.sampler_descriptor.min_filter = FilterMode::Linear; texture.sampler_descriptor.mipmap_filter = FilterMode::Linear; diff --git a/bevy_kayak_ui/src/render/unified/image/extract.rs b/bevy_kayak_ui/src/render/unified/image/extract.rs new file mode 100644 index 0000000000000000000000000000000000000000..dfee468af1f4e1c74d456fcae9c05923d5b9f68f --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/image/extract.rs @@ -0,0 +1,60 @@ +use bevy::{ + math::Vec2, + prelude::{Commands, Res}, + render2::color::Color, + sprite2::Rect, +}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType}, + BevyContext, ImageManager, +}; + +pub fn extract_images( + mut commands: Commands, + context: Res<BevyContext>, + image_manager: Res<ImageManager>, +) { + let render_commands = if let Ok(context) = context.kayak_context.read() { + context.widget_manager.build_render_primitives() + } else { + vec![] + }; + + let image_commands: Vec<&RenderPrimitive> = render_commands + .iter() + .filter(|command| matches!(command, RenderPrimitive::Image { .. })) + .collect::<Vec<_>>(); + + let mut extracted_quads = Vec::new(); + for render_primitive in image_commands { + let (layout, handle) = match render_primitive { + RenderPrimitive::Image { layout, handle } => (layout, handle), + _ => panic!(""), + }; + + extracted_quads.push(ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, layout.posy), + max: Vec2::new(layout.posx + layout.width, layout.posy + layout.height), + }, + color: Color::WHITE, + vertex_index: 0, + char_id: 0, + z_index: layout.z_index, + font_handle: None, + quad_type: UIQuadType::Image, + type_index: 0, + border_radius: (0.0, 0.0, 0.0, 0.0), + image: image_manager + .get_handle(handle) + .and_then(|a| Some(a.clone_weak())), + uv_max: None, + uv_min: None, + }, + }); + } + commands.spawn_batch(extracted_quads); +} diff --git a/bevy_kayak_ui/src/render/unified/image/image_manager.rs b/bevy_kayak_ui/src/render/unified/image/image_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..0493f8dbc26fe3322fc17b0b36dc4e379383675f --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/image/image_manager.rs @@ -0,0 +1,34 @@ +use bevy::{prelude::Handle, render2::texture::Image, utils::HashMap}; + +#[derive(Debug, Clone)] +pub struct ImageManager { + count: u16, + mapping: HashMap<u16, Handle<Image>>, + reverse_mapping: HashMap<Handle<Image>, u16>, +} + +impl ImageManager { + pub fn new() -> Self { + Self { + count: 0, + mapping: HashMap::default(), + reverse_mapping: HashMap::default(), + } + } + + pub fn get(&mut self, image_handle: &Handle<Image>) -> u16 { + if let Some(id) = self.reverse_mapping.get(image_handle) { + return *id; + } else { + let id = self.count; + self.count += 1; + self.mapping.insert(id, image_handle.clone()); + self.reverse_mapping.insert(image_handle.clone_weak(), id); + return id; + } + } + + pub fn get_handle(&self, id: &u16) -> Option<&Handle<Image>> { + self.mapping.get(id) + } +} diff --git a/bevy_kayak_ui/src/render/unified/image/mod.rs b/bevy_kayak_ui/src/render/unified/image/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..549b0970c0884f0c0d46223f54a25a8a2ef87cbd --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/image/mod.rs @@ -0,0 +1,19 @@ +use bevy::{ + prelude::Plugin, + render2::{RenderApp, RenderStage}, +}; + +mod extract; +mod image_manager; +pub use image_manager::ImageManager; + +pub struct ImageRendererPlugin; + +impl Plugin for ImageRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.insert_resource(ImageManager::new()); + + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, extract::extract_images); + } +} diff --git a/bevy_kayak_ui/src/render/unified/mod.rs b/bevy_kayak_ui/src/render/unified/mod.rs index 37aeb58243aa976470811d76a83d949f8754ca34..152bdd7eb1a26c1f0fb6ac31e5fa56be8b51fdb9 100644 --- a/bevy_kayak_ui/src/render/unified/mod.rs +++ b/bevy_kayak_ui/src/render/unified/mod.rs @@ -9,7 +9,11 @@ use crate::render::{ unified::pipeline::{DrawUI, QuadMeta, UnifiedPipeline}, }; +use self::pipeline::ImageBindGroups; + pub mod font; +pub mod image; +mod nine_patch; mod pipeline; mod quad; @@ -26,6 +30,7 @@ impl Plugin for UnifiedRenderPlugin { let render_app = app.sub_app(RenderApp); render_app + .init_resource::<ImageBindGroups>() .init_resource::<UnifiedPipeline>() .init_resource::<QuadMeta>() .add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads) @@ -41,6 +46,8 @@ impl Plugin for UnifiedRenderPlugin { .add(draw_quad); app.add_plugin(font::TextRendererPlugin) - .add_plugin(quad::QuadRendererPlugin); + .add_plugin(quad::QuadRendererPlugin) + .add_plugin(image::ImageRendererPlugin) + .add_plugin(nine_patch::NinePatchRendererPlugin); } } diff --git a/bevy_kayak_ui/src/render/unified/nine_patch/extract.rs b/bevy_kayak_ui/src/render/unified/nine_patch/extract.rs new file mode 100644 index 0000000000000000000000000000000000000000..fdd47979cc4b97c650c0d5be41b93f8eda1f8d5d --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/nine_patch/extract.rs @@ -0,0 +1,260 @@ +use bevy::{ + math::Vec2, + prelude::{Assets, Commands, Res}, + render2::{color::Color, texture::Image}, + sprite2::Rect, +}; +use kayak_core::render_primitive::RenderPrimitive; + +use crate::{ + render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType}, + BevyContext, ImageManager, +}; + +pub fn extract_nine_patch( + mut commands: Commands, + context: Res<BevyContext>, + image_manager: Res<ImageManager>, + images: Res<Assets<Image>>, +) { + let render_commands = if let Ok(context) = context.kayak_context.read() { + context.widget_manager.build_render_primitives() + } else { + vec![] + }; + + let image_commands: Vec<&RenderPrimitive> = render_commands + .iter() + .filter(|command| matches!(command, RenderPrimitive::NinePatch { .. })) + .collect::<Vec<_>>(); + + let mut extracted_quads = Vec::new(); + for render_primitive in image_commands { + let (layout, handle, border) = match render_primitive { + RenderPrimitive::NinePatch { + layout, + handle, + border, + } => (layout, handle, border), + _ => panic!(""), + }; + + let image_handle = image_manager + .get_handle(handle) + .and_then(|a| Some(a.clone_weak())); + + let image = images.get(image_handle.as_ref().unwrap()); + + if image.is_none() { + dbg!("Uh oh no image! :("); + continue; + } + + let image_size = image + .and_then(|i| { + Some(Vec2::new( + i.texture_descriptor.size.width as f32, + i.texture_descriptor.size.height as f32, + )) + }) + .unwrap(); + + let extracted_quad_template = ExtractedQuad { + rect: Rect { + min: Vec2::ZERO, + max: Vec2::ZERO, + }, + color: Color::WHITE, + vertex_index: 0, + char_id: 0, + z_index: layout.z_index, + font_handle: None, + quad_type: UIQuadType::Image, + type_index: 0, + border_radius: (0.0, 0.0, 0.0, 0.0), + image: image_handle, + uv_max: None, + uv_min: None, + }; + + // TOP + let top_left_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, layout.posy), + max: Vec2::new(layout.posx + border.left, layout.posy + border.top), + }, + uv_min: Some(Vec2::new(0.0, border.top / image_size.y)), + uv_max: Some(Vec2::new(border.left / image_size.x, 0.0)), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(top_left_quad); + + let top_right_pos_x = (layout.posx + layout.width) - border.left; + let top_right_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(top_right_pos_x, layout.posy), + max: Vec2::new(top_right_pos_x + border.left, layout.posy + border.top), + }, + uv_min: Some(Vec2::new( + (image_size.x - border.left) / image_size.x, + border.top / image_size.y, + )), + uv_max: Some(Vec2::new(1.0, 0.0)), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(top_right_quad); + + let top_middle_pos_x = layout.posx + border.left; + let top_middle_size_x = layout.width - (border.left + border.right); + let top_middle_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(top_middle_pos_x, layout.posy), + max: Vec2::new( + top_middle_pos_x + top_middle_size_x, + layout.posy + border.top, + ), + }, + uv_min: Some(Vec2::new( + border.left / image_size.x, + border.top / image_size.y, + )), + uv_max: Some(Vec2::new((image_size.x - border.left) / image_size.x, 0.0)), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(top_middle_quad); + + // Bottom + let bottom_y_pos = layout.posy + (layout.height - border.bottom); + let bottom_left_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, bottom_y_pos), + max: Vec2::new(layout.posx + border.left, bottom_y_pos + border.bottom), + }, + uv_min: Some(Vec2::new(0.0, 1.0)), + uv_max: Some(Vec2::new( + border.left / image_size.x, + (image_size.y - border.bottom) / image_size.y, + )), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(bottom_left_quad); + + let bottom_right_pos_x = (layout.posx + layout.width) - border.left; + let bottom_right_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(bottom_right_pos_x, bottom_y_pos), + max: Vec2::new(bottom_right_pos_x + border.left, bottom_y_pos + border.top), + }, + uv_min: Some(Vec2::new((image_size.x - border.left) / image_size.x, 1.0)), + uv_max: Some(Vec2::new( + 1.0, + (image_size.y - border.bottom) / image_size.y, + )), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(bottom_right_quad); + + let bottom_middle_pos_x = layout.posx + border.left; + let bottom_middle_size_x = layout.width - (border.left + border.right); + let bottom_middle_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(bottom_middle_pos_x, bottom_y_pos), + max: Vec2::new( + bottom_middle_pos_x + bottom_middle_size_x, + bottom_y_pos + border.top, + ), + }, + uv_min: Some(Vec2::new(border.left / image_size.x, 1.0)), + uv_max: Some(Vec2::new( + (image_size.x - border.left) / image_size.x, + (image_size.y - border.bottom) / image_size.y, + )), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(bottom_middle_quad); + + // Left + Right center + let left_middle_pos_y = layout.posy + border.top; + let left_middle_size_y = layout.height - (border.top + border.bottom); + let left_middle_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, left_middle_pos_y), + max: Vec2::new( + layout.posx + border.left, + left_middle_pos_y + left_middle_size_y, + ), + }, + uv_min: Some(Vec2::new( + 0.0, + (image_size.y - border.bottom) / image_size.y, + )), + uv_max: Some(Vec2::new( + border.left / image_size.x, + border.top / image_size.y, + )), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(left_middle_quad); + + let right_middle_pos_x = layout.posx + (layout.width - border.right); + let right_middle_pos_y = layout.posy + border.top; + let right_middle_size_y = layout.height - (border.top + border.bottom); + let right_middle_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(right_middle_pos_x, right_middle_pos_y), + max: Vec2::new( + right_middle_pos_x + border.left, + right_middle_pos_y + right_middle_size_y, + ), + }, + uv_min: Some(Vec2::new( + (image_size.x - border.left) / image_size.x, + (image_size.y - border.bottom) / image_size.y, + )), + uv_max: Some(Vec2::new(1.0, border.top / image_size.y)), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(right_middle_quad); + + // Last quad in middle. + let middle_pos_x = layout.posx + border.left; + let middle_pos_y = layout.posy + border.top; + let middle_size_x = layout.width - (border.left + border.right); + let middle_size_y = layout.height - (border.top + border.bottom); + let middle_quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(middle_pos_x, middle_pos_y), + max: Vec2::new(middle_pos_x + middle_size_x, middle_pos_y + middle_size_y), + }, + uv_min: Some(Vec2::new( + border.left / image_size.x, + border.top / image_size.y, + )), + uv_max: Some(Vec2::new( + (image_size.x - border.right) / image_size.x, + (image_size.y - border.bottom) / image_size.y, + )), + ..extracted_quad_template.clone() + }, + }; + extracted_quads.push(middle_quad); + } + commands.spawn_batch(extracted_quads); +} diff --git a/bevy_kayak_ui/src/render/unified/nine_patch/mod.rs b/bevy_kayak_ui/src/render/unified/nine_patch/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..dfaec008a1ea982b0d6781f75d977a639ea2fc6b --- /dev/null +++ b/bevy_kayak_ui/src/render/unified/nine_patch/mod.rs @@ -0,0 +1,15 @@ +use bevy::{ + prelude::Plugin, + render2::{RenderApp, RenderStage}, +}; + +mod extract; + +pub struct NinePatchRendererPlugin; + +impl Plugin for NinePatchRendererPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, extract::extract_nine_patch); + } +} diff --git a/bevy_kayak_ui/src/render/unified/pipeline.rs b/bevy_kayak_ui/src/render/unified/pipeline.rs index f110ca4fd630842b5def4579c9e89c7c55378df9..a787e7152ef7f32786639027c9b2d475acdfc320 100644 --- a/bevy_kayak_ui/src/render/unified/pipeline.rs +++ b/bevy_kayak_ui/src/render/unified/pipeline.rs @@ -4,26 +4,30 @@ use bevy::{ lifetimeless::{Read, SQuery, SRes}, SystemState, }, - math::{const_vec3, Mat4, Quat, Vec3, Vec4}, + math::{const_vec3, Mat4, Quat, Vec2, Vec3, Vec4}, prelude::{Bundle, Component, Entity, FromWorld, Handle, Query, Res, ResMut, World}, render2::{ color::Color, + render_asset::RenderAssets, render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent, - BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages, - BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, DynamicUniformVec, - FragmentState, FrontFace, MultisampleState, PolygonMode, PrimitiveState, - PrimitiveTopology, RenderPipelineCache, RenderPipelineDescriptor, Shader, ShaderStages, - TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute, - VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, + BlendComponent, BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, + BufferUsages, BufferVec, CachedPipelineId, ColorTargetState, ColorWrites, + DynamicUniformVec, Extent3d, FragmentState, FrontFace, MultisampleState, PolygonMode, + PrimitiveState, PrimitiveTopology, RenderPipelineCache, RenderPipelineDescriptor, + SamplerDescriptor, Shader, ShaderStages, TextureDescriptor, TextureDimension, + TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, + TextureViewDimension, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, + VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage}, + texture::{BevyDefault, GpuImage, Image}, view::{ViewUniformOffset, ViewUniforms}, }, sprite2::Rect, + utils::HashMap, }; use bytemuck::{Pod, Zeroable}; use crevice::std140::AsStd140; @@ -35,9 +39,11 @@ use crate::render::ui_pass::TransparentUI; pub struct UnifiedPipeline { view_layout: BindGroupLayout, types_layout: BindGroupLayout, - pub(crate) image_layout: BindGroupLayout, + pub(crate) font_image_layout: BindGroupLayout, + image_layout: BindGroupLayout, pipeline: CachedPipelineId, empty_font_texture: (GpuImage, BindGroup), + default_image: (GpuImage, BindGroup), } const QUAD_VERTEX_POSITIONS: &[Vec3] = &[ @@ -87,6 +93,33 @@ impl FromWorld for UnifiedPipeline { label: Some("ui_types_layout"), }); + // Used by fonts + let font_image_layout = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2Array, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("text_image_layout"), + }); + let image_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ BindGroupLayoutEntry { @@ -95,7 +128,7 @@ impl FromWorld for UnifiedPipeline { ty: BindingType::Texture { multisampled: false, sample_type: TextureSampleType::Float { filterable: false }, - view_dimension: TextureViewDimension::D2Array, + view_dimension: TextureViewDimension::D2, }, count: None, }, @@ -109,7 +142,7 @@ impl FromWorld for UnifiedPipeline { count: None, }, ], - label: Some("text_image_layout"), + label: Some("image_layout"), }); let vertex_buffer_layout = VertexBufferLayout { @@ -139,7 +172,7 @@ impl FromWorld for UnifiedPipeline { ], }; - let empty_font_texture = FontTextureCache::get_empty(&render_device, &image_layout); + let empty_font_texture = FontTextureCache::get_empty(&render_device, &font_image_layout); let pipeline_desc = RenderPipelineDescriptor { vertex: VertexState { @@ -171,8 +204,9 @@ impl FromWorld for UnifiedPipeline { }), layout: Some(vec![ view_layout.clone(), - image_layout.clone(), + font_image_layout.clone(), types_layout.clone(), + image_layout.clone(), ]), primitive: PrimitiveState { front_face: FrontFace::Ccw, @@ -189,20 +223,73 @@ impl FromWorld for UnifiedPipeline { mask: !0, alpha_to_coverage_enabled: false, }, - label: Some("quad_pipeline".into()), + label: Some("unified_pipeline".into()), }; + let texture_descriptor = TextureDescriptor { + label: Some("font_texture_array"), + size: Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }; + + let sampler_descriptor = SamplerDescriptor::default(); + + let texture = render_device.create_texture(&texture_descriptor); + let sampler = render_device.create_sampler(&sampler_descriptor); + + let texture_view = texture.create_view(&TextureViewDescriptor { + label: Some("font_texture_array_view"), + format: None, + dimension: Some(TextureViewDimension::D2), + aspect: bevy::render2::render_resource::TextureAspect::All, + base_mip_level: 0, + base_array_layer: 0, + mip_level_count: None, + array_layer_count: None, + }); + + let image = GpuImage { + texture, + sampler, + texture_view, + }; + + let binding = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("text_image_bind_group"), + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&image.sampler), + }, + ], + layout: &image_layout, + }); + UnifiedPipeline { pipeline: pipeline_cache.queue(pipeline_desc), view_layout, - image_layout, + font_image_layout, empty_font_texture, types_layout, + image_layout, + default_image: (image, binding), } } } -#[derive(Bundle)] +#[derive(Debug, Bundle)] pub struct ExtractQuadBundle { pub(crate) extracted_quad: ExtractedQuad, } @@ -211,9 +298,10 @@ pub struct ExtractQuadBundle { pub enum UIQuadType { Quad, Text, + Image, } -#[derive(Component)] +#[derive(Debug, Component, Clone)] pub struct ExtractedQuad { pub rect: Rect, pub color: Color, @@ -224,6 +312,9 @@ pub struct ExtractedQuad { pub quad_type: UIQuadType, pub type_index: u32, pub border_radius: (f32, f32, f32, f32), + pub image: Option<Handle<Image>>, + pub uv_min: Option<Vec2>, + pub uv_max: Option<Vec2>, } #[repr(C)] @@ -259,6 +350,11 @@ impl Default for QuadMeta { } } +#[derive(Default)] +pub struct ImageBindGroups { + values: HashMap<Handle<Image>, BindGroup>, +} + pub fn prepare_quads( render_device: Res<RenderDevice>, render_queue: Res<RenderQueue>, @@ -266,7 +362,7 @@ pub fn prepare_quads( mut extracted_quads: Query<&mut ExtractedQuad>, ) { let extracted_sprite_len = extracted_quads.iter_mut().len(); - // dont create buffers when there are no quads + // don't create buffers when there are no quads if extracted_sprite_len == 0 { return; } @@ -275,6 +371,7 @@ pub fn prepare_quads( sprite_meta.types_buffer.reserve(2, &render_device); let quad_type_offset = sprite_meta.types_buffer.push(QuadType { t: 0 }); let text_type_offset = sprite_meta.types_buffer.push(QuadType { t: 1 }); + let image_type_offset = sprite_meta.types_buffer.push(QuadType { t: 2 }); sprite_meta .types_buffer .write_buffer(&render_device, &render_queue); @@ -292,29 +389,33 @@ pub fn prepare_quads( match extracted_sprite.quad_type { UIQuadType::Quad => extracted_sprite.type_index = quad_type_offset, UIQuadType::Text => extracted_sprite.type_index = text_type_offset, + UIQuadType::Image => extracted_sprite.type_index = image_type_offset, }; + let uv_min = extracted_sprite.uv_min.unwrap_or(Vec2::ZERO); + let uv_max = extracted_sprite.uv_max.unwrap_or(Vec2::ONE); + let bottom_left = Vec4::new( - 0.0, - 1.0, + uv_min.x, + uv_max.y, extracted_sprite.char_id as f32, extracted_sprite.border_radius.0, ); let top_left = Vec4::new( - 0.0, - 0.0, + uv_min.x, + uv_min.y, extracted_sprite.char_id as f32, extracted_sprite.border_radius.1, ); let top_right = Vec4::new( - 1.0, - 0.0, + uv_max.x, + uv_min.y, extracted_sprite.char_id as f32, extracted_sprite.border_radius.2, ); let bottom_right = Vec4::new( - 1.0, - 1.0, + uv_max.x, + uv_max.y, extracted_sprite.char_id as f32, extracted_sprite.border_radius.3, ); @@ -362,6 +463,9 @@ pub fn queue_quads( quad_pipeline: Res<UnifiedPipeline>, mut extracted_sprites: Query<(Entity, &ExtractedQuad)>, mut views: Query<&mut RenderPhase<TransparentUI>>, + mut image_bind_groups: ResMut<ImageBindGroups>, + unified_pipeline: Res<UnifiedPipeline>, + gpu_images: Res<RenderAssets<Image>>, ) { if let Some(type_binding) = sprite_meta.types_buffer.binding() { sprite_meta.types_bind_group = @@ -384,9 +488,34 @@ pub fn queue_quads( label: Some("quad_view_bind_group"), layout: &quad_pipeline.view_layout, })); + let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap(); for mut transparent_phase in views.iter_mut() { for (entity, quad) in extracted_sprites.iter_mut() { + if let Some(image_handle) = quad.image.as_ref() { + image_bind_groups + .values + .entry(image_handle.clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&image_handle).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &gpu_image.texture_view, + ), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("ui_image_bind_group"), + layout: &unified_pipeline.image_layout, + }) + }); + } transparent_phase.add(TransparentUI { draw_function: draw_quad, pipeline: quad_pipeline.pipeline, @@ -404,6 +533,7 @@ pub struct DrawUI { SRes<UnifiedPipeline>, SRes<RenderPipelineCache>, SRes<FontTextureCache>, + SRes<ImageBindGroups>, SQuery<Read<ViewUniformOffset>>, SQuery<Read<ExtractedQuad>>, )>, @@ -425,8 +555,16 @@ impl Draw<TransparentUI> for DrawUI { view: Entity, item: &TransparentUI, ) { - let (quad_meta, unified_pipeline, pipelines, font_texture_cache, views, quads) = - self.params.get(world); + let ( + quad_meta, + unified_pipeline, + pipelines, + font_texture_cache, + image_bind_groups, + views, + quads, + ) = self.params.get(world); + let view_uniform = views.get(view).unwrap(); let quad_meta = quad_meta.into_inner(); let extracted_quad = quads.get(item.entity).unwrap(); @@ -445,20 +583,31 @@ impl Draw<TransparentUI> for DrawUI { &[extracted_quad.type_index], ); + let unified_pipeline = unified_pipeline.into_inner(); if let Some(font_handle) = extracted_quad.font_handle.as_ref() { if let Some(image_bindings) = font_texture_cache.into_inner().bind_groups.get(font_handle) { pass.set_bind_group(1, image_bindings, &[]); } else { - pass.set_bind_group( - 1, - &unified_pipeline.into_inner().empty_font_texture.1, - &[], - ); + pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]); } } else { - pass.set_bind_group(1, &unified_pipeline.into_inner().empty_font_texture.1, &[]); + pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]); + } + + if let Some(image_handle) = extracted_quad.image.as_ref() { + pass.set_bind_group( + 3, + &image_bind_groups + .into_inner() + .values + .get(image_handle) + .unwrap(), + &[], + ); + } else { + pass.set_bind_group(3, &unified_pipeline.default_image.1, &[]); } pass.draw( diff --git a/bevy_kayak_ui/src/render/unified/quad/extract.rs b/bevy_kayak_ui/src/render/unified/quad/extract.rs index 20a4c8ec13fefe9e1b58418ce777747b23dbe1ab..1a9fcf4ebb4a6e9aec7dd775cb8e8ea78c412002 100644 --- a/bevy_kayak_ui/src/render/unified/quad/extract.rs +++ b/bevy_kayak_ui/src/render/unified/quad/extract.rs @@ -47,6 +47,9 @@ pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) { quad_type: UIQuadType::Quad, type_index: 0, border_radius: *border_radius, + image: None, + uv_max: None, + uv_min: None, }, }); } diff --git a/bevy_kayak_ui/src/render/unified/shader.wgsl b/bevy_kayak_ui/src/render/unified/shader.wgsl index 90a5af20ebd89422ce43604ca5bf70c367e7f87c..c32bbb8bba67c9980d61303ece5c64507e576933 100644 --- a/bevy_kayak_ui/src/render/unified/shader.wgsl +++ b/bevy_kayak_ui/src/render/unified/shader.wgsl @@ -13,7 +13,6 @@ struct QuadType { [[group(2), binding(0)]] var<uniform> quad_type: QuadType; - struct VertexOutput { [[builtin(position)]] position: vec4<f32>; [[location(0)]] color: vec4<f32>; @@ -39,17 +38,20 @@ fn vertex( out.uv = vertex_uv.xyz; out.size = vertex_pos_size.zw; out.border_radius = vertex_uv.w; - return out; } [[group(1), binding(0)]] -var sprite_texture: texture_2d_array<f32>; +var font_texture: texture_2d_array<f32>; [[group(1), binding(1)]] -var sprite_sampler: sampler; +var font_sampler: sampler; -let RADIUS: f32 = 0.1; +[[group(3), binding(0)]] +var image_texture: texture_2d<f32>; +[[group(3), binding(1)]] +var image_sampler: sampler; +let RADIUS: f32 = 0.1; fn sd_box_rounded( frag_coord: vec2<f32>, @@ -71,9 +73,6 @@ fn sd_box_rounded( [[stage(fragment)]] fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { - var pxRange = 2.5; - var tex_dimensions = textureDimensions(sprite_texture); - var msdfUnit = vec2<f32>(pxRange, pxRange) / vec2<f32>(f32(tex_dimensions.x), f32(tex_dimensions.y)); if (quad_type.t == 0) { var dist = sd_box_rounded( in.position.xy, @@ -89,7 +88,10 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { return vec4<f32>(in.color.rgb, dist); } if (quad_type.t == 1) { - var x = textureSample(sprite_texture, sprite_sampler, vec2<f32>(in.uv.x, 1.0 - in.uv.y), i32(in.uv.z)); + var px_range = 2.5; + var tex_dimensions = textureDimensions(font_texture); + var msdf_unit = vec2<f32>(px_range, px_range) / vec2<f32>(f32(tex_dimensions.x), f32(tex_dimensions.y)); + var x = textureSample(font_texture, font_sampler, vec2<f32>(in.uv.x, 1.0 - in.uv.y), i32(in.uv.z)); var v = max(min(x.r, x.g), min(max(x.r, x.g), x.b)); var c = v; //remap(v); @@ -103,10 +105,14 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> { // var w = fwidth(c); // var a = smoothStep(0.5 - w, 0.5 + w, c); - var sigDist = (c - 0.5) * dot(msdfUnit, 0.5 / fwidth(in.uv.xy)); - var a = clamp(sigDist + 0.5, 0.0, 1.0); + var sig_dist = (c - 0.5) * dot(msdf_unit, 0.5 / fwidth(in.uv.xy)); + var a = clamp(sig_dist + 0.5, 0.0, 1.0); return vec4<f32>(in.color.rgb, a); } + if (quad_type.t == 2) { + var color = textureSample(image_texture, image_sampler, vec2<f32>(in.uv.x, in.uv.y)); + return vec4<f32>(color.rgb * in.color.rgb, color.a * in.color.a); + } return in.color; } \ No newline at end of file diff --git a/examples/counter.rs b/examples/counter.rs index cc68c29f9d3e098742bb8abda0c0612ba704cb7e..2d946636fd5a0c8df3f5ea2a756004935f95e975 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -7,6 +7,7 @@ use bevy::{ use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, UICameraBundle}; use kayak_components::{Button, Text, Window}; use kayak_core::{ + context::KayakContext, styles::{Style, StyleProp, Units}, EventType, Index, OnEvent, }; @@ -14,7 +15,7 @@ use kayak_ui::components::App; use kayak_ui::core::{rsx, widget}; #[widget] -fn Counter() { +fn Counter(context: &mut KayakContext) { let count = { let x = context.create_state(0i32).unwrap(); *x diff --git a/examples/full_ui.rs b/examples/full_ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed9b075b7d9ab309ec9cbce1c64d52c74e627ba2 --- /dev/null +++ b/examples/full_ui.rs @@ -0,0 +1,197 @@ +use bevy::{ + math::Vec2, + prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut, World}, + window::{WindowDescriptor, Windows}, + PipelinedDefaultPlugins, +}; +use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle}; +use kayak_components::{NinePatch, Text}; +use kayak_core::{ + context::KayakContext, + layout_cache::Space, + styles::{LayoutType, Style, StyleProp, Units}, + widget, Children, EventType, Index, OnEvent, +}; +use kayak_ui::components::App; +use kayak_ui::core::rsx; + +#[widget] +fn BlueButton(context: KayakContext, children: Children, styles: Option<Style>) { + let (blue_button_handle, blue_button_hover_handle) = { + let world = context.get_global_state::<World>(); + if world.is_err() { + return; + } + + let mut world = world.unwrap(); + + let (handle1, handle2) = { + let asset_server = world.get_resource::<AssetServer>().unwrap(); + let handle1: Handle<bevy::render2::texture::Image> = + asset_server.load("../assets/kenny/buttonSquare_blue.png"); + let handle2: Handle<bevy::render2::texture::Image> = + asset_server.load("../assets/kenny/buttonSquare_blue_pressed.png"); + + (handle1, handle2) + }; + + let mut image_manager = world.get_resource_mut::<ImageManager>().unwrap(); + let blue_button_handle = image_manager.get(&handle1); + let blue_button_hover_handle = image_manager.get(&handle2); + + (blue_button_handle, blue_button_hover_handle) + }; + + let current_button_handle = *context.create_state::<u16>(blue_button_handle).unwrap(); + dbg!(current_button_handle); + + let button_styles = Style { + width: StyleProp::Value(Units::Pixels(200.0)), + height: StyleProp::Value(Units::Pixels(50.0)), + padding_left: StyleProp::Value(Units::Stretch(1.0)), + padding_right: StyleProp::Value(Units::Stretch(1.0)), + padding_top: StyleProp::Value(Units::Stretch(1.0)), + padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + ..styles.clone().unwrap_or_default() + }; + + let button_id = self.get_id(); + let on_event = OnEvent::new(move |context, event| match event.event_type { + EventType::Click => { + dbg!("Clicked!"); + context.set_current_id(button_id); + context.set_state::<u16>(blue_button_hover_handle); + } + _ => { + context.set_state::<u16>(blue_button_handle); + } + }); + + rsx! { + <NinePatch + border={Space { + left: 10.0, + right: 10.0, + top: 10.0, + bottom: 10.0, + }} + handle={current_button_handle} + styles={Some(button_styles)} + on_event={Some(on_event)} + > + {children} + </NinePatch> + } +} + +fn startup( + mut commands: Commands, + windows: Res<Windows>, + asset_server: Res<AssetServer>, + mut image_manager: ResMut<ImageManager>, +) { + commands.spawn_bundle(UICameraBundle::new()); + + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + + let handle: Handle<bevy::render2::texture::Image> = asset_server.load("kenny/panel_brown.png"); + let panel_brown_handle = image_manager.get(&handle); + + let context = BevyContext::new(window_size.x, window_size.y, |styles, context| { + // Hack to trick the proc macro for right now.. + let parent_id: Option<Index> = None; + + let nine_patch_styles = Style { + layout_type: StyleProp::Value(LayoutType::Column), + width: StyleProp::Value(Units::Pixels(512.0)), + height: StyleProp::Value(Units::Pixels(512.0)), + padding_left: StyleProp::Value(Units::Stretch(1.0)), + padding_right: StyleProp::Value(Units::Stretch(1.0)), + padding_top: StyleProp::Value(Units::Stretch(1.0)), + padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + ..Style::default() + }; + + let app_styles = Style { + padding_left: StyleProp::Value(Units::Stretch(1.0)), + padding_right: StyleProp::Value(Units::Stretch(1.0)), + padding_top: StyleProp::Value(Units::Stretch(1.0)), + padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + ..styles.clone() + }; + + let header_styles = Style { + width: StyleProp::Value(Units::Pixels(432.0)), + height: StyleProp::Value(Units::Pixels(64.0)), + bottom: StyleProp::Value(Units::Stretch(1.0)), + ..Style::default() + }; + + let play_button_styles = Style { + width: StyleProp::Value(Units::Pixels(54.0)), + height: StyleProp::Value(Units::Pixels(45.0)), + ..Style::default() + }; + + let options_button_text_styles = Style { + width: StyleProp::Value(Units::Pixels(102.0)), + height: StyleProp::Value(Units::Pixels(45.0)), + ..Style::default() + }; + + let options_button_styles = Style { + top: StyleProp::Value(Units::Pixels(15.0)), + ..Style::default() + }; + + rsx! { + <App styles={Some(app_styles)}> + <NinePatch + styles={Some(nine_patch_styles)} + border={Space { + left: 30.0, + right: 30.0, + top: 30.0, + bottom: 30.0, + }} + handle={panel_brown_handle} + > + <Text + styles={Some(header_styles)} + size={50.0} + content={"Name My Game Plz".to_string()} + /> + <BlueButton> + <Text styles={Some(play_button_styles)} size={30.0} content={"Play".to_string()} /> + </BlueButton> + <BlueButton styles={Some(options_button_styles)}> + <Text styles={Some(options_button_text_styles)} size={30.0} content={"Options".to_string()} /> + </BlueButton> + <BlueButton styles={Some(options_button_styles)}> + <Text styles={Some(play_button_styles)} size={30.0} content={"Quit".to_string()} /> + </BlueButton> + </NinePatch> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/examples/image.rs b/examples/image.rs new file mode 100644 index 0000000000000000000000000000000000000000..82b571488c00812cdf44bd7713a2d89daec7beed --- /dev/null +++ b/examples/image.rs @@ -0,0 +1,55 @@ +use bevy::{ + math::Vec2, + prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut}, + window::{WindowDescriptor, Windows}, + PipelinedDefaultPlugins, +}; +use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle}; +use kayak_components::Image; +use kayak_core::Index; +use kayak_ui::components::App; +use kayak_ui::core::rsx; + +fn startup( + mut commands: Commands, + windows: Res<Windows>, + asset_server: Res<AssetServer>, + mut image_manager: ResMut<ImageManager>, +) { + commands.spawn_bundle(UICameraBundle::new()); + + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + + let handle: Handle<bevy::render2::texture::Image> = asset_server.load("panel.png"); + let ui_image_handle = image_manager.get(&handle); + + let context = BevyContext::new(window_size.x, window_size.y, |styles, context| { + // Hack to trick the proc macro for right now.. + let parent_id: Option<Index> = None; + rsx! { + <App styles={Some(styles.clone())}> + <Image handle={ui_image_handle} /> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/examples/nine_patch.rs b/examples/nine_patch.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a14f1a27c83ee6cbbd0787d724d51b7e6216c88 --- /dev/null +++ b/examples/nine_patch.rs @@ -0,0 +1,98 @@ +use bevy::{ + math::Vec2, + prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut}, + window::{WindowDescriptor, Windows}, + PipelinedDefaultPlugins, +}; +use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle}; +use kayak_components::NinePatch; +use kayak_core::{ + layout_cache::Space, + styles::{Style, StyleProp, Units}, + Index, +}; +use kayak_ui::components::App; +use kayak_ui::core::rsx; + +fn startup( + mut commands: Commands, + windows: Res<Windows>, + asset_server: Res<AssetServer>, + mut image_manager: ResMut<ImageManager>, +) { + commands.spawn_bundle(UICameraBundle::new()); + + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + + let handle: Handle<bevy::render2::texture::Image> = asset_server.load("panel.png"); + let ui_image_handle = image_manager.get(&handle); + + let context = BevyContext::new(window_size.x, window_size.y, |styles, context| { + // Hack to trick the proc macro for right now.. + let parent_id: Option<Index> = None; + + // The border prop splits up the image into 9 quadrants like so: + // 1----2----3 + // | | + // 4 9 5 + // | | + // 6----7----8 + // The sizes of sprites for a 15 pixel border are as follows: + // TopLeft = (15, 15) + // TopRight = (15, 15) + // LeftCenter = (15, image_height) + // RightCenter = (15, image_height) + // TopCenter = (image_width, 15) + // BottomCenter = (image_width, 15) + // BottomRight = (15, 15) + // BottomLeft = (15, 15) + // Middle = ( + // 30 being left border + right border + // image_width - 30 + // 30 being top border + bottom border + // image_height - 30 + // ) + // + + let nine_patch_styles = Style { + width: StyleProp::Value(Units::Pixels(512.0)), + height: StyleProp::Value(Units::Pixels(512.0)), + ..Style::default() + }; + + rsx! { + <App styles={Some(styles.clone())}> + <NinePatch + styles={Some(nine_patch_styles)} + border={Space { + left: 15.0, + right: 15.0, + top: 15.0, + bottom: 15.0, + }} + handle={ui_image_handle} + /> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/kayak_components/src/image.rs b/kayak_components/src/image.rs new file mode 100644 index 0000000000000000000000000000000000000000..39cd65c8b5d4c25aa3c4edf30987d5b1ae440dd8 --- /dev/null +++ b/kayak_components/src/image.rs @@ -0,0 +1,20 @@ +use kayak_core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, +}; + +#[widget] +pub fn Image(handle: u16, children: Children) { + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Image { handle: *handle }), + ..styles.clone().unwrap_or_default() + }); + + rsx! { + <> + {children} + </> + } +} diff --git a/kayak_components/src/lib.rs b/kayak_components/src/lib.rs index a9b99766d6e61c29c1503cd17f00736729100b40..d59d930cb5444d0dce0afb700c63c784fa066f6e 100644 --- a/kayak_components/src/lib.rs +++ b/kayak_components/src/lib.rs @@ -2,6 +2,8 @@ mod app; mod background; mod button; mod clip; +mod image; +mod nine_patch; mod text; mod window; @@ -9,5 +11,7 @@ pub use app::*; pub use background::*; pub use button::*; pub use clip::*; +pub use image::*; +pub use nine_patch::*; pub use text::*; pub use window::*; diff --git a/kayak_components/src/nine_patch.rs b/kayak_components/src/nine_patch.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d652ab6ebd018b6f4de826fa0b99e491e2318b5 --- /dev/null +++ b/kayak_components/src/nine_patch.rs @@ -0,0 +1,24 @@ +use kayak_core::{ + layout_cache::Space, + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, +}; + +#[widget] +pub fn NinePatch(handle: u16, border: Space, children: Children) { + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::NinePatch { + handle: *handle, + border: *border, + }), + ..styles.clone().unwrap_or_default() + }); + + rsx! { + <> + {children} + </> + } +} diff --git a/kayak_core/cargo.toml b/kayak_core/Cargo.toml similarity index 100% rename from kayak_core/cargo.toml rename to kayak_core/Cargo.toml diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs index b18aa0ef8c79a7e4d7ea06e36515673cc6e77441..59d1d52306c005762e0cd1b2e17ee12983186f25 100644 --- a/kayak_core/src/context.rs +++ b/kayak_core/src/context.rs @@ -1,6 +1,5 @@ -use std::collections::HashMap; - use resources::Ref; +use std::collections::HashMap; use crate::{node::NodeIndex, widget_manager::WidgetManager, Event, EventType, Index, InputEvent}; @@ -9,6 +8,7 @@ pub struct KayakContext { current_id: crate::Index, pub widget_manager: WidgetManager, last_mouse_position: (f32, f32), + pub global_state: resources::Resources, } impl std::fmt::Debug for KayakContext { @@ -26,6 +26,7 @@ impl KayakContext { current_id: crate::Index::default(), widget_manager: WidgetManager::new(), last_mouse_position: (0.0, 0.0), + global_state: resources::Resources::default(), } } @@ -37,6 +38,7 @@ impl KayakContext { &mut self, initial_state: T, ) -> Option<Ref<T>> { + dbg!(self.current_id); if self.component_states.contains_key(&self.current_id) { let states = self.component_states.get_mut(&self.current_id).unwrap(); if !states.contains::<T>() { @@ -61,10 +63,12 @@ impl KayakContext { } pub fn set_state<T: resources::Resource + Clone>(&mut self, state: T) { + dbg!(self.current_id); if self.component_states.contains_key(&self.current_id) { let states = self.component_states.get(&self.current_id).unwrap(); if states.contains::<T>() { let mut mutate_t = states.get_mut::<T>().unwrap(); + dbg!("Mutating state!"); self.widget_manager.dirty_nodes.push(self.current_id); *mutate_t = state; } else { @@ -78,6 +82,20 @@ impl KayakContext { } } + pub fn set_global_state<T: resources::Resource>(&mut self, state: T) { + self.global_state.insert(state); + } + + pub fn get_global_state<T: resources::Resource>( + &mut self, + ) -> Result<resources::RefMut<T>, resources::CantGetResource> { + self.global_state.get_mut::<T>() + } + + pub fn take_global_state<T: resources::Resource>(&mut self) -> Option<T> { + self.global_state.remove::<T>() + } + pub fn render(&mut self) { let dirty_nodes = self.widget_manager.dirty_nodes.clone(); for node_index in dirty_nodes { diff --git a/kayak_core/src/layout_cache.rs b/kayak_core/src/layout_cache.rs index c843be7332f35973d4dd37bd0a0d705ab5925962..9b31c08c8b4bfdedb474ceec4c6843787e504380 100644 --- a/kayak_core/src/layout_cache.rs +++ b/kayak_core/src/layout_cache.rs @@ -20,7 +20,7 @@ impl Rect { } } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct Space { pub left: f32, pub right: f32, diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index 47009cc660238e08faefea66b6ec6915f67e2674..fe2cc7fdd442504763f3577a545d658567a24908 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -22,6 +22,8 @@ pub use input_event::*; pub use kayak_render_macros::{render, rsx, widget}; pub use widget::Widget; +pub use resources::Resources; + pub type Children = Option<Arc<dyn Fn(Option<crate::Index>, &mut crate::context::KayakContext) + Send + Sync>>; diff --git a/kayak_core/src/render_command.rs b/kayak_core/src/render_command.rs index 31f034efcb8c6cbb7cd804da0fa2984288b605e4..c717a2f23aa8706a9bb8a17c163f1fdb668c59f4 100644 --- a/kayak_core/src/render_command.rs +++ b/kayak_core/src/render_command.rs @@ -1,3 +1,5 @@ +use crate::layout_cache::Space; + #[derive(Debug, Clone, PartialEq)] pub enum RenderCommand { Empty, @@ -9,6 +11,13 @@ pub enum RenderCommand { size: f32, font: u16, }, + Image { + handle: u16, + }, + NinePatch { + border: Space, + handle: u16, + }, } impl Default for RenderCommand { diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs index 3208f6c1f4fe58fe4f16149fb7e02d5fd279659d..54b3224809697e9b06401be84786874482bed734 100644 --- a/kayak_core/src/render_primitive.rs +++ b/kayak_core/src/render_primitive.rs @@ -1,6 +1,6 @@ use crate::{ color::Color, - layout_cache::Rect, + layout_cache::{Rect, Space}, render_command::RenderCommand, styles::{Style, StyleProp}, }; @@ -23,6 +23,15 @@ pub enum RenderPrimitive { content: String, font: u16, }, + Image { + layout: Rect, + handle: u16, + }, + NinePatch { + border: Space, + layout: Rect, + handle: u16, + }, } impl RenderPrimitive { @@ -31,6 +40,8 @@ impl RenderPrimitive { RenderPrimitive::Clip { layout, .. } => *layout = new_layout, RenderPrimitive::Quad { layout, .. } => *layout = new_layout, RenderPrimitive::Text { layout, .. } => *layout = new_layout, + RenderPrimitive::Image { layout, .. } => *layout = new_layout, + RenderPrimitive::NinePatch { layout, .. } => *layout = new_layout, _ => (), } } @@ -68,6 +79,15 @@ impl From<&Style> for RenderPrimitive { content, font, }, + RenderCommand::Image { handle } => Self::Image { + layout: Rect::default(), + handle, + }, + RenderCommand::NinePatch { handle, border } => Self::NinePatch { + border, + layout: Rect::default(), + handle, + }, } } } diff --git a/kayak_core/src/styles.rs b/kayak_core/src/styles.rs index 17266dc971130ef194237f890258de14561921b6..a1671dcf387ec682d5e48b2cb8dd3d6f20d7de92 100644 --- a/kayak_core/src/styles.rs +++ b/kayak_core/src/styles.rs @@ -49,6 +49,10 @@ pub struct Style { pub padding_right: StyleProp<Units>, pub padding_top: StyleProp<Units>, pub padding_bottom: StyleProp<Units>, + pub margin_left: StyleProp<Units>, + pub margin_right: StyleProp<Units>, + pub margin_top: StyleProp<Units>, + pub margin_bottom: StyleProp<Units>, } impl Default for Style { @@ -70,6 +74,10 @@ impl Default for Style { padding_right: StyleProp::Default, padding_top: StyleProp::Default, padding_bottom: StyleProp::Default, + margin_left: StyleProp::Default, + margin_right: StyleProp::Default, + margin_top: StyleProp::Default, + margin_bottom: StyleProp::Default, } } } @@ -164,5 +172,21 @@ impl Style { StyleProp::Inherit => self.padding_bottom = other.padding_bottom.clone(), _ => (), } + match self.margin_left { + StyleProp::Inherit => self.margin_left = other.margin_left.clone(), + _ => (), + } + match self.margin_right { + StyleProp::Inherit => self.margin_right = other.margin_right.clone(), + _ => (), + } + match self.margin_top { + StyleProp::Inherit => self.margin_top = other.margin_top.clone(), + _ => (), + } + match self.margin_bottom { + StyleProp::Inherit => self.margin_bottom = other.margin_bottom.clone(), + _ => (), + } } } diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs index 97ed85ebe3fdb3f78e6f8eab4e98726d97f87058..dd141b2000b077425f0792b647b780af24ebd17e 100644 --- a/kayak_core/src/widget_manager.rs +++ b/kayak_core/src/widget_manager.rs @@ -139,6 +139,7 @@ impl WidgetManager { right: crate::styles::StyleProp::Default, top: crate::styles::StyleProp::Default, width: crate::styles::StyleProp::Default, + ..Style::default() }; for dirty_node_index in self.dirty_render_nodes.drain(..) { let dirty_widget = self.current_widgets[dirty_node_index].as_ref().unwrap(); diff --git a/kayak_render_macros/src/children.rs b/kayak_render_macros/src/children.rs index f948e6727cb1817f9b96c964ab349c6d73ab9471..88dd8dc2c2a1054ae1cc984d18162f0a75749c86 100644 --- a/kayak_render_macros/src/children.rs +++ b/kayak_render_macros/src/children.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use crate::{ arc_function::build_arc_function, attribute::Attribute, @@ -95,60 +97,101 @@ impl Children { _ => { let mut iter = children_quotes.iter(); - let first = iter.next().unwrap(); - let second = iter.next().unwrap(); - - let first = build_arc_function(quote! { child1 }, first.clone(), true, 0); - let second = build_arc_function(quote! { child2 }, second.clone(), true, 1); + // First get shared and non-shared attributes.. + let mut child_attributes_list = Vec::new(); + for i in 0..children_quotes.len() { + child_attributes_list.push(self.get_clonable_attributes(i)); + } - let children_attributes0: Vec<_> = self.get_clonable_attributes(0); - let children_attributes1: Vec<_> = self.get_clonable_attributes(1); - let (children_attributes0, children_attributes1, matching) = - handle_tuple_attributes(&children_attributes0, &children_attributes1); + let mut all_attributes = HashSet::new(); + for child_attributes in child_attributes_list.iter() { + for child_attribute in child_attributes { + all_attributes.insert(child_attribute.to_string()); + } + } - let base_matching: Vec<proc_macro2::TokenStream> = matching + let base_matching: Vec<proc_macro2::TokenStream> = all_attributes .iter() - .map(|a| { - format!("base_{}", a.to_string()) - .to_string() - .parse() - .unwrap() - }) + .map(|a| format!("base_{}", a).to_string().parse().unwrap()) .collect(); + let all_attributes: Vec<proc_macro2::TokenStream> = + all_attributes.iter().map(|a| a.parse().unwrap()).collect(); + let base_clone = quote! { - #(let #base_matching = #matching.clone();)* + #(let #base_matching = #all_attributes.clone();)* }; let base_clones_inner = quote! { - #(let #matching = #base_matching.clone();)* + #(let #all_attributes = #base_matching.clone();)* }; - let cloned_attrs0 = quote! { - #(let #children_attributes0 = #children_attributes0.clone();)* - }; - let cloned_attrs1 = quote! { - #(let #children_attributes1 = #children_attributes1.clone();)* - }; + let mut output = Vec::new(); + output.push(quote! { #base_clone }); + for i in 0..children_quotes.len() { + output.push(quote! { #base_clones_inner }); + let name: proc_macro2::TokenStream = format!("child{}", i).parse().unwrap(); + let child = + build_arc_function(quote! { #name }, children_quotes[i].clone(), true, i); + output.push(quote! { #child }); + } - let tuple_of_tuples = iter.fold( - quote! { - #base_clone - #cloned_attrs0 - #base_clones_inner - #first - #base_clones_inner - #cloned_attrs1 - #second - }, - |renderable, current| quote!((#renderable, #current)), - ); + // let first = iter.next().unwrap(); + // let second = iter.next().unwrap(); + + // let first = build_arc_function(quote! { child1 }, first.clone(), true, 0); + // let second = build_arc_function(quote! { child2 }, second.clone(), true, 1); + + // let children_attributes0: Vec<_> = self.get_clonable_attributes(0); + // let children_attributes1: Vec<_> = self.get_clonable_attributes(1); + // let (children_attributes0, children_attributes1, matching) = + // handle_tuple_attributes(&children_attributes0, &children_attributes1); + + // let base_matching: Vec<proc_macro2::TokenStream> = matching + // .iter() + // .map(|a| { + // format!("base_{}", a.to_string()) + // .to_string() + // .parse() + // .unwrap() + // }) + // .collect(); + + // let base_clone = quote! { + // #(let #base_matching = #matching.clone();)* + // }; + + // let base_clones_inner = quote! { + // #(let #matching = #base_matching.clone();)* + // }; + + // let cloned_attrs0 = quote! { + // #(let #children_attributes0 = #children_attributes0.clone();)* + // }; + // let cloned_attrs1 = quote! { + // #(let #children_attributes1 = #children_attributes1.clone();)* + // }; + + // let tuple_of_tuples = iter.fold( + // quote! { + // #base_clone + // #cloned_attrs0 + // #base_clones_inner + // #first + // #base_clones_inner + // #cloned_attrs1 + // #second + // }, + // |renderable, current| quote!((#renderable, #current)), + // ); quote! { Some(std::sync::Arc::new(move |parent_id: Option<kayak_core::Index>, context: &mut kayak_core::context::KayakContext| { - #tuple_of_tuples + #(#output)* })) } + + // quote! {} } } } diff --git a/kayak_render_macros/src/widget.rs b/kayak_render_macros/src/widget.rs index 96d0c703d7621ead8ad5486a71f92e408038bbb9..1368e1875e472706f59f014cb63c7f042cde7c8e 100644 --- a/kayak_render_macros/src/widget.rs +++ b/kayak_render_macros/src/widget.rs @@ -10,7 +10,6 @@ use crate::{tags::OpenTag, widget_attributes::WidgetAttributes}; #[derive(Debug, Clone)] pub struct Widget { - name: syn::Path, pub attributes: WidgetAttributes, pub children: Children, declaration: TokenStream, @@ -82,7 +81,6 @@ impl Widget { }; Ok(Widget { - name, attributes: open_tag.attributes, children, declaration,