pax_global_header00006660000000000000000000000064151345024120014507gustar00rootroot0000000000000052 comment=71515e83eb7ad0ec921c913e1d5772e5fe55daa9 hyprwm-hyprtoolkit-71515e8/000077500000000000000000000000001513450241200157045ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/.clang-format000066400000000000000000000034161513450241200202630ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All hyprwm-hyprtoolkit-71515e8/.clang-tidy000066400000000000000000000072071513450241200177460ustar00rootroot00000000000000WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' FormatStyle: 'file' Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-forward-declaration-namespace, -bugprone-forward-declaration-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, -bugprone-assignment-in-if-condition, concurrency-*, -concurrency-mt-unsafe, cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-init-variables, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-reinterpret-cast, google-global-names-in-headers, -google-readability-casting, google-runtime-operator, misc-*, -misc-unused-parameters, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-include-cleaner, -misc-use-anonymous-namespace, -misc-const-correctness, modernize-*, -modernize-return-braced-init-list, -modernize-use-trailing-return-type, -modernize-use-using, -modernize-use-override, -modernize-avoid-c-arrays, -modernize-macro-to-enum, -modernize-loop-convert, -modernize-use-nodiscard, -modernize-pass-by-value, -modernize-use-auto, performance-*, -performance-avoid-endl, -performance-unnecessary-value-param, portability-std-allocator-const, readability-*, -readability-function-cognitive-complexity, -readability-function-size, -readability-identifier-length, -readability-magic-numbers, -readability-uppercase-literal-suffix, -readability-braces-around-statements, -readability-redundant-access-specifiers, -readability-else-after-return, -readability-container-data-pointer, -readability-implicit-bool-conversion, -readability-avoid-nested-conditional-operator, -readability-redundant-member-init, -readability-redundant-string-init, -readability-avoid-const-params-in-decls, -readability-named-parameter, -readability-convert-member-functions-to-static, -readability-qualified-auto, -readability-make-member-function-const, -readability-isolate-declaration, -readability-inconsistent-declaration-parameter-name, -clang-diagnostic-error, CheckOptions: performance-for-range-copy.WarnOnAllAutoCopies: true performance-inefficient-string-concatenation.StrictMode: true readability-braces-around-statements.ShortStatementLines: 0 readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.ClassIgnoredRegexp: I.* readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? readability-identifier-naming.EnumCase: CamelCase readability-identifier-naming.EnumPrefix: e readability-identifier-naming.EnumConstantCase: UPPER_CASE readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.NamespaceCase: CamelCase readability-identifier-naming.StructPrefix: S readability-identifier-naming.StructCase: CamelCase hyprwm-hyprtoolkit-71515e8/.github/000077500000000000000000000000001513450241200172445ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/.github/workflows/000077500000000000000000000000001513450241200213015ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/.github/workflows/nix.yml000066400000000000000000000033011513450241200226170ustar00rootroot00000000000000name: Build on: [push, pull_request, workflow_dispatch] jobs: nix: strategy: matrix: package: - hyprtoolkit - hyprtoolkit-with-tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Nix uses: nixbuild/nix-quick-install-action@v31 with: nix_conf: | keep-env-derivations = true keep-outputs = true - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }}- # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G # do purge caches purge: true # purge all versions of the cache purge-prefixes: nix-${{ runner.os }}- # created more than this number of seconds ago purge-created: 0 # or, last accessed more than this number of seconds ago # relative to the start of the `Post Restore and save Nix store` phase purge-last-accessed: 0 # except any version with the key that is the same as the `primary-key` purge-primary-key: never # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build run: nix build .#${{ matrix.package }} --print-build-logs --keep-going hyprwm-hyprtoolkit-71515e8/.gitignore000066400000000000000000000012371513450241200176770ustar00rootroot00000000000000CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts Testing Makefile cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake _deps CMakeUserPresets.json # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #cmake-build-* build/ .vscode/ .cache/ protocols/*.cpp protocols/*.hpp *.inc src/renderer/gl/shaders/Shaders.hpphyprwm-hyprtoolkit-71515e8/CMakeLists.txt000066400000000000000000000144701513450241200204520ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} HYPRTOOLKIT_VERSION) add_compile_definitions(HYPRTOOLKIT_VERSION="${HYPRTOOLKIT_VERSION}") project( hyprtoolkit VERSION ${HYPRTOOLKIT_VERSION} DESCRIPTION "A modern C++ Wayland-native GUI toolkit") include(CTest) include(CheckIncludeFile) include(GNUInstallDirs) set(PREFIX ${CMAKE_INSTALL_PREFIX}) set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED COMPONENTS "GLES3") find_package(hyprwayland-scanner 0.4.0 REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols egl hyprutils>=0.11.0 hyprlang>=0.6.0 pixman-1 libdrm gbm xkbcommon pango cairo pangocairo iniparser hyprgraphics>=0.3.0 aquamarine>=0.10.0) configure_file(hyprtoolkit.pc.in hyprtoolkit.pc @ONLY) set(CMAKE_CXX_STANDARD 23) add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wpedantic) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring hyprtoolkit in Debug") add_compile_definitions(HYPRTOOLKIT_DEBUG) set(BUILD_TESTING ON) else() add_compile_options(-O3) message(STATUS "Configuring hyprtoolkit in Release") set(BUILD_TESTING OFF) endif() add_compile_definitions(HT_HIDDEN=public) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") execute_process(COMMAND ./scripts/generateShaderIncludes.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_library(hyprtoolkit SHARED ${SRCFILES}) target_include_directories( hyprtoolkit PUBLIC "./include" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") set_target_properties(hyprtoolkit PROPERTIES VERSION ${HYPRTOOLKIT_VERSION} SOVERSION 5) target_link_libraries(hyprtoolkit PUBLIC OpenGL::EGL OpenGL::OpenGL PkgConfig::deps) check_include_file("sys/timerfd.h" HAS_TIMERFD) pkg_check_modules(epoll IMPORTED_TARGET epoll-shim) if(NOT HAS_TIMERFD AND epoll_FOUND) target_link_libraries(hyprtoolkit PUBLIC PkgConfig::epoll) endif() check_include_file("sys/inotify.h" HAS_INOTIFY) pkg_check_modules(inotify IMPORTED_TARGET libinotify) if(NOT HAS_INOTIFY AND inotify_FOUND) target_link_libraries(hyprtoolkit PUBLIC PkgConfig::inotify) endif() # Protocols pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) message( STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}") function(protocolNew protoPath protoName external) if(external) set(path ${CMAKE_SOURCE_DIR}/${protoPath}) else() set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) endif() add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(hyprtoolkit PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) endfunction() function(protocolWayland) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp COMMAND hyprwayland-scanner --wayland-enums --client ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(hyprtoolkit PRIVATE protocols/wayland.cpp protocols/wayland.hpp) endfunction() protocolwayland() protocolnew("stable/xdg-shell" "xdg-shell" false) protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) protocolnew("staging/fractional-scale" "fractional-scale-v1" false) protocolnew("stable/viewporter" "viewporter" false) protocolnew("staging/cursor-shape" "cursor-shape-v1" false) protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false) protocolnew("stable/tablet" "tablet-v2" false) protocolnew("unstable/text-input" "text-input-unstable-v3" false) protocolnew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false) protocolnew("protocols" "wlr-layer-shell-unstable-v1" true) # Tests if(BUILD_TESTING) enable_testing() # test apps add_custom_target(tests) add_executable(simpleWindow "tests/SimpleWindow.cpp") target_link_libraries(simpleWindow PRIVATE PkgConfig::deps hyprtoolkit) add_dependencies(tests simpleWindow) add_executable(dialog "tests/Dialog.cpp") target_link_libraries(dialog PRIVATE PkgConfig::deps hyprtoolkit) add_dependencies(tests dialog) add_executable(controls "tests/Controls.cpp") target_link_libraries(controls PRIVATE PkgConfig::deps hyprtoolkit) add_dependencies(tests controls) add_executable(simpleSessionLock "tests/SimpleSessionLock.cpp") target_link_libraries(simpleSessionLock PRIVATE PkgConfig::deps hyprtoolkit) add_dependencies(tests simpleSessionLock) # GTest find_package(GTest CONFIG REQUIRED) include(GoogleTest) file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/unit/*.cpp") add_executable(hyprtoolkit_tests ${TESTFILES}) target_compile_options(hyprtoolkit_tests PRIVATE --coverage) target_link_options(hyprtoolkit_tests PRIVATE --coverage) target_include_directories( hyprtoolkit_tests PUBLIC "./include" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") target_link_libraries( hyprtoolkit_tests PRIVATE hyprtoolkit GTest::gtest_main OpenGL::EGL OpenGL::OpenGL PkgConfig::deps) gtest_discover_tests(hyprtoolkit_tests) # Add coverage to hyprtoolkit target_compile_options(hyprtoolkit PRIVATE --coverage) target_link_options(hyprtoolkit PRIVATE --coverage) endif() # Installation install(TARGETS hyprtoolkit) install(DIRECTORY "include/hyprtoolkit" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${CMAKE_BINARY_DIR}/hyprtoolkit.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) hyprwm-hyprtoolkit-71515e8/LICENSE000066400000000000000000000027371513450241200167220ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2025, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hyprwm-hyprtoolkit-71515e8/README.md000066400000000000000000000012421513450241200171620ustar00rootroot00000000000000## hyprtoolkit A modern C++ Wayland-native GUI toolkit ![](./assets/preview.png) ## What Hyprtoolkit is Hyprtoolkit is designed to be a small, simple, and modern C++ toolkit for making wayland GUI apps, with a few goals: - Simple C++ API for making a GUI app - Smooth animations - Easy usage - Simple system theming ## Building Standard CMake build: ```sh cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` ``` ### What Hyprtoolkit is not Hyprtoolkit is not: - cross-platform - packed with crazy features hyprwm-hyprtoolkit-71515e8/VERSION000066400000000000000000000000051513450241200167470ustar00rootroot000000000000000.5.3hyprwm-hyprtoolkit-71515e8/assets/000077500000000000000000000000001513450241200172065ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/assets/preview.png000066400000000000000000016137761513450241200214220ustar00rootroot00000000000000PNG  IHDR 8 pHYsnu>iTXtXML:com.adobe.xmp ОvIDATx[ɱ  -7JW>;w_g·gtgԆޔ{}3pkMR,2~At('B=aaaa$0 0Qhu:ÓDʼn:jṹׯOLL |>{ttd*Jjz%=OXFcT_YYY[[YeffLMMyXD=xfFnjn2ի`||/ׯQ ߐDQV%h^Tի(Ib(Q(n۷gggyAN.CgD# Q՝;wpCkPQEs(*p̭[\f ˜UL) f<4C|ZqZ 0 s O0 0 0 0 0 0 z vuKr< trH$&[Nd2mLJlTvPZv]^l8j rQ@pJXLT[RAt G[ QQVZ- ?`Th#c}S:N 0 \{0 0 0 0 0 0 01C>+v^Z-03&h4|>JvwwDRtxfY,jRX,-.ȡ6 jPj#ZSPlp f[BT$8N6'j)Jݠ.Q t:4 ,st(ɰ@C|n= 0 ]3 0 0 0 0 0 0@xj)&F1J"YT*t:DP@1vzZ#jT4ՒV]Ptǹ(In%Mғ2RID5Xx(^)E C!shh@Ct.32c 0 =3 0 0 0 0 0 0Ǡxrt%VZ-76EO)L&NgY%9p!%pjvF#g2c0VD"N/+J"GFF^/‰\.͖v[$͆j~?jxnB!Á85jecl$)n zfD=- L&}9o A=*!(N9gtt4`nXĜc6ua3 0 0 0 0 0 0~ (lrx$ʠ](N FCף Rpl6WL&XT:JB?F @aNm)8hoQ'rzéFa.1ߏt:afjyPfaa>10 0 0 0 0 0 |0j5=P(M.x-vZf+z(#*J1I*1f{U| N\.gXf6%\.Vj5A(I$O@(@tVWr$8#GB?*FX(( y̰ 0 |Bcaaaaaa`WmZ'/5PUղ٬^tSz)`0xBcH5{C}C 6Cn.O j8<a׊/N>mb W2EVF{Nm | Y2 ON{Fz5~> !P6s!ߦdT[bL#~:QQ( W_d=Yeec!tMHJd0`'(,ZX,RD"NсV!Ju’*Ԑc Rq< 9BsrxHPgQ[R ʣZtd2UbDGd2IC '$aatNltڝjh6NYza5[- k{ThYV͆z`StgTm\Yxn7K7sVVn4+sf}fXjq7IO'Z@ . tjժr'x0/;JOZDavj4PQg-4 6څNhT*zMڥ*y绢i_a0²ªjţTJؓذʕJS vIg)0Gha EAn%b$X|ȇZ <@TƑtT*e2d2GAsE1NĘ!ԆrFI{t\t#cZz^42xG.Ǝ'8]c(]Rql?VB]3!Atr6}aa=f(ub6{/;QkܽF>}J&j `'nڔw>lߗ/^.//jUenQmݝϞR)Qh2ٹ9ls׷6]vtrA?xE|:K HP~ 5߼ukjz:N?{+'4C,nݻcXٓ'x$eC7+7 BjAhc2TJ ,ȏ?87?mksͼ=jE5Do߆AWlM?}+ۻ3s HjKZ bQo$ ;N+?Od2 `0ܻwoJ9Dz=nϧ!#䙙>DZ)2R$9X0 0_3/5Rzn'I/'"g"n+*TeۭV+clV*DM7\R'e_JJKšA 3SVQ"Hn1 !f4) lUsD7V=Ɋ⋃DXht"Q(LP({&70 0q;2D,^ŎV*u_W ,y]^^7 Bcc0awwvҙq8GG"H̐KᭉiX+r8/,Ln4MMM-.^}Ǐvwa+70VˑHƍA>R{X6"|Vpdd²_i6js݃H\,|Z!5Lhʕ+XVJF}wիWNgw̨T^{bbbǿ! |&''HW]{{x\.Af 뽲͍\6K6`K߽w͛hWh=rqqq}m_~Y[[T*l}Y{v8O*MC0G{^&al\hlٹFZͦNccX6¾}Ia`Ba]qvCj"v[mH2 0 v(!^J>CR 4JL-hr?_xbogRJR"twӖ( [ t.ݱ@cD tIo$Gh( W%$iDFJs*FG8px 0 9` \zcόC{* nܺՆ_(dwJzj2t3,k6??uaafR,0nwttt||bJc;\v 8&922i'&Idz.#b;lbh&ԟ7ov퍍òT;"ё~.c3bȬl MB;NsttraXߨVfRONNloogi`X5u A`t=HP^f#)h5Zׯk{fN*fy\f2O}! MTʒwo2̥B+W676>yBu >K~I$?߾{@$K"MF"w@$ꥥz9akF;vP0pv!ws14t+eNRr\%yҹ]f%wfJS89 PTu+֪ȱF1~2.a35 ſ PBsBT2IJ0sεkO?T*d&Ν-^Z,~'0*@0XV7ץylqBO JTO71\ 0`Z Bn4v[IQD/ KZR@؅.*!OQ'{]ڎWk'N@@1^xE-񎨬E{ý6M4S]FJ`pC! C#f!.]Qeg2pUaJo7pv: W7O/D.S=v+RITq25b6Gp8\Vvw5ǓNd6!e:Ev%IR=H톐Aqȋ"B PhB%oRIRHz\-Pc/؞P Ɔq"?~:<8@ ze:Ft=A!_(WʼM0dL\q<&X,&8###kkmXi CQ4C0aHرJ"[ "ی,7Ey(?)GoaJၘe9c8իWPmfXhf>ǩl6[(a͢ HNj4l6v) Za\zzAW5V4|d'4ivn+^[.H>ߐ/0>117;D`j`4$~*2: eC`qqvwkj2x5N D,&"ފNA?,F#2t/?V##wޅǏbG4d_ jXLU[ffg1NjoZh]na{R[Jd2K͛PcvyЫu9z=h 0`Ar ;0n߾ dyd'OmR$fI)?4"9 @| 0 0i((^A|>O7*XAU}/)oB`a*1\Vuh4,/fgoܸI3lVHǚ;[]YQnf###GGGk++p(Y\0kzRih4l^YӉpz\N%#U2<+n~͛7TX0 cc7nBIgmu*,..FGG{^XLsss=D"9lhd67 T.\. ןEۑhT\> P3+ ѱ~_,?dq:7nԒ]5VR^+alfKzKħn'p8$%9C!e>oooo}uV 0f2Rf2q?|txH1!?"8!RZBիNl~ FWO(A(pYqAalVB8Re7o`'Lކ$TzQ h_Xla`*6tO wh bH$_~r&OPUa}y %]:+CK \(DX(@:[kI^E`@IH QamnmnBXb!1tx^699mw*2T*drX$Ot")c! aaE lZNaNABU_CEAӰ`eJ%Q"%-a{03HT*HơVS)cN=.,.LgO':(WUdzph,G%++vl6(45=;:jb_}YRZv? E";Jd2\*+_CP8‰333SSS:,Mvd0J8A Z6Lj͖jjP@67QFӍf3NE43驝F=;7'R *lf^Rx ?"L{r 0|zOh\__.NOffg.(Q"cno?cssCʠVK\u <,mooӅhHH dj'ԆwQD[Bd6-ۃ]YX ,5J*AUccd`n9-DʦѠscӡ]WFy KIY1G CHl`($aMA+`4O?H$V_bY#0Nh)Fآ2'نHʠx;'BST)˙ٙT:Ր eC["faKD[TݩJؖu=ɰSr&zs ٥&w"?c8%Zқ7v}VR)P1v|4[[[h)yk0_'[}>dud!L,/CR&,Az4_~ }P !28 #H +U Kt$'[ӁA 0 0̥6a?T*x<^,hv8%6-h`a,\QTV 0 O=fsɸ,X h3ͰFFNjZP(v ⦼QTU919 *6]I-(<;? "nu2"<~6{pp;BؑNtzbXnE+J`X,yEyl|[wwt:mw8 z{2 (wfIds hhf5OLTܺu'kK\*MAp-,(jdRlXw*P`οSLwJil^3%\H4 X?cmd U [-N."eXLw,e&yy%Kƻd j‘INk$27P~ALzBWф@j@&h݇L +zjQnk |Krшjd(PN$8 `B^`>Eaap >VҗN(X3Hݱ]|Uۦjvtxd}~_$D`G$)dR|h4:>>xZ 2ѹ`L Jw{ld;l:kǠ$$n= E-Y0̞|}bt Ғ4$?;; ^EdM]z=ºdNe2_~y_^*Ad2+++_*P0h#:&E(,XH$)w̿󢮒R48 0 0y̐ MX 0/cBgZ2 MYŠaaYb~_F}ǻB'7nFC+6b6;NXs40T6fXD|yǪbV*v-(`1#CP!:NBu:Fnk5ZNFmt&N57el&sxt cAǁutzziv;:JD̓{ө fFDZr^yRA+zncV+>W;AOG5\^Z,t/]a Do@"##Z l6C2ҷfXL5a^JLNLH$MG"`8T7vww!^reH/e8rE}8sln^>VCQd4Ioa%D$ Z7x*9"Y<>>2ld2V3=ap kV^hT3i`b=7 moo!ڂ@0`dqJe}uLMOի;kkP]:eVu:LZc-J}>W+S` ttDzH%Q3IICdIy ÙHaIw9N 0 0 J=K3- ,w 0 `NӨ dYuz]Vn#l6Nh1W=;-b:wdte6KkPNr#*nUTk9TZɉht$ύD#8j͍$—Q:P4K&>@*r$nPx>0]?~S^z#-lϟ?q'Fb,-ɻN%ݩwYѽЦ5>gs/^_,1 w*0_ŸPՕЬCU#,gZqHu$0R J?" 1un~+tH t{< ⽽=mUXaPz#aa ^ wzvttL$FūW0͓SSW.A2o4@&ڝû;;8SW*b0»/DDzbC%Y < ZrW^G"tl^ߗ|R(vфb!*{rKpV\szall,IN5dJGLZ D~Շ ~@KKKJiZL;d2zrkcN0e߇hDQHq"xj%ng6:ݮ펌DGGX&j#h$q:jUHʎ=g49"Fٹ9(Ñ|.#R e*"  F@9@Η'n>V619yƍϟ & ͛w_Z_[/v0ߐ B`bmmn>X,iBId}k0)d%uO6R|=ZN8݅BfQa˂ ?37;>> k'Fra|MГzq{{`>y(f[\\|A X___]Y- Q+J}ufd6kdG2QKD%ƃ2FX02V#C8`.PH.ãh4 Z)f$z*ksUiehOEi!83m(0tVU}H``J!9m.B053=>1~.'mԦ,O$0߇w"\6nvD^H } 0 |Т/Zذ8x0SaN`Bt:driiiddd|b&by jה\_X!h6GGGw˭M |p82L\Fggg hT*nC.. S]W*vww\+aBp>3::b|h4j5t =wwwJVɩ)J"Hi4DP˗`:_XhL(p T֕tWޝ`2gfg-f<ɫ׮\gϞѧIr\[xB^HJ8Hr.(ŚJ&WP6Hj<^o h4P9H^jڐ'p JƎ$~/Jďp⬃}?ͭ^!qкt"sڭ~8<~t:1(4V܍_~'O`tiNې;>">_R7=Eş_y("P8|(qܽ!t[[[z> B4܎;7>/ B4H-8R.`<3P}]ug{{vnO!L 'N///' E(g"r_+_iYmkע##Dʆ7t 6$,ps.Z7]/:.5ؑn=Z9cWLP|^XL6K>aH0s Z :ncrl,)K"X)k8~ H |}^/'!niQC.8з*D ?ÂM3FQ$ Ha*KgO5K[Ltl "D ?b B٪aa>ڌy0 0 _Vh4jueyj>ᇑqRDfX(>Of2כL&Vh_x׋= GJ&q'7o$%yƍ{OL4 4H^<VVRf貨VǻDzJ7Q1hlll}=x0|s\X ,//L*Zzv_z?ajfV FAe X(LNMmo/~qvfrnr7f++l|o ecyFA`T|Ff#&Im̂Vŋ_u}mj:\Ν嚝VUH1( @6b33k㫫+_BjOnA%T"Q.#_qrAaL N-GGGffLQ: nspx_Zm$t:lD-ioowksLeCȻ̿*VMNLэ[:4L#_~uYbecokP B W|C`88|mU$Nava={l2߅%dii 2J$׮-(WPiS#yKX,R)C04{|w\v- ZK4"f؜(zH93;s="Cogͭ'|BHWx< ɽvP8,z(d`vH30;En6n1o^ UaC67\nwA& B"D"~JD+~?`3障6~S_ ^=a3 2kҙeɂL=x  b cj5=螲]U͝wB"=;FS}J$qtY߿ݫ7^-T?~dX$c1+0 |}(e1310 0i,|WvRo4yIKK^_loj5 ֪UF^9N&\onl6!v1A.;g2d*U.ŎFfIN=9!8E-qt)_%IJE+5dOvwv[J2,ɤ̷b}1|/?Q~9|тa.NER6::jZ!e^ķ! Y"ta [-(0NkkkP$)dB<)dnN;:\C@D7kko^-ҖV]*2l8lwwAG@BqHqp[jВA}Ȭl8n?ʶN\qakDGGG;7y$Qr;A I4"+t&2#' O(.]I6}5_RhT, W{DgJ T piX*ݮP6l2Ny?!Â}2ZDajE7v6@YI)JA0YØ^L&tr/_>{#BZE+ d030`)Za:&\t, !&S@y NJ IT^2(l4TrI b6C >y+ݣ#T7L>)wE8 LXà7 C*%|6'?i)!\?Vjh?_a)b`Ev<-Hgda~:G# G0=a2T2,s\r%ڢ9NϧjL^&]-vzJp68].^vq<')ht\n4^r4uRy6=Fd2%Tl44]6 MBf(EWv;iw8(9׋hbțf eLf3NA ]@ xbO>+hc(YGĵLχ))@>C hN\FN庲7[,4h4ws]KST+FpR3~qӉv*$ϼӋ^JA|6B=R$N^ȓ oE׋Ņ|pjTkP? `Zx S`T*VkZ\ fsRaxD$myM6ae_r et *a1×&(,70H"j˻.(waB:+08' NRd(B!a 9v.CrpfK:(޳lZ ZRӣ#*$eQNS׋ -4``9Α2g(H4:1@pJ&8xΝ?/񏝝4blj4A+KaD+1 a^ Ż8NIf<4|= %: EyRL{tCydt0ðFZC93,t8<  DA~!5VM/?T HBaai.G1叻O7zpJƓeF^aa3~:c{mDt}bj,^U ̩(qy_gRՠg7'7(0xkް6҇3Q]W;}gul85~FT߈Ʃ W*y%[M)Ŕݽ -*/ִ&1wNQt̶Yo7hV9ފwogQql rw )\jiӴS('94],wo@A]֙͝cfl}|WN2Ұ2ft:?V??{z}o[$O+Oٽ0_]A_'_mpGpFv̿ aa>+?s:$0 0)t<߭DpwYL{RǛ-χg tw49Y7Uѥ=2FW"D}'}bNO7Fja .aH^TCAV?NΔS;xN7w^s'z{^m:aU{qCڥOwCwc jB# THqջ 3M턆g{"GL~j 哭 2y0eO{zj% R}XNgY:^A$ceaO y #@5_oVnlfa1Öo}߬7w߳j!neտs?x0)IEvam$GׁaoR.Ӈ0.8} 1{|]#gOaa>aaaaaa! Oȥ/?;0 0̇3 0 0 0 0 0 0?}~ػaa~10 0 0 0 0 0 2/&&aa=faaaaaa0a0 0 0 0 0 0 0 0 010 0 0 0 0 0 0 0 |_ 0 0 0 0 0 0 0 0 }3 0 0 0 0 0 0 0 0{0 0 0 0 0 0 0 0 010 0 0 0 0 0 0 0 |_ 0 0 0 0 0 0 0 0 })$0̥@-y`dc$%aPaaaaa]h4Z0_u{=<)a p2`z>|  |*E[aaaa3D +|/|tn_X0TI:a~v:VB!N&m\kRBO0 0 0 0 \vcFD2FNWѭ0_WkZ׷Zvx~Y h0౯V~2mhH?JO` mf%a6G }tNh!0 0 0%R/s%0 \jwZ7 4:]{J^}7ART(N&VVZ_'| ⢑фA ] X?+ƾOdl? AB w;ʖXBkP4Co.^5NN3 0 0 \eo=f~_'ӺG 0e ɤh2H*D^AŽv{j5&`4 o5J/PzOF#t2NʝFg5jFQo0%*kZiz`v'00 s~=fĵ AN2 sVJjz}1_d ~*xN'R?rx/ J(9I \B wWE;00 0 ,F~񛅕c|3QFqἭDƖ×28 |¬Lr{'o%0 s.=fd:Vv '#`KV/'p̍0OW#_jPFCY2O~eoaaHӲ?B-ꃬ7!RW]\9p`h?Aj`R*ڭ寊&>&jF忞 ; 0eỎ1#ܴa/7\f+npjvEв~2̷^Z-6A*Uj[ufaa>cAX@klt:nj-Kwb(38hD* 4f!eKhۈ 0f 0n5b8ͦ7sXKN1!F3z3|Z%+qR WH/^gacϚq5;50̥F˛Z-F2vΧ0?S? ݵeзGa˫r]aaC-JzTKdv;Vd2 Z;Vl4ZR)xl67#j4x^Ԁj.rZEU 8f:NjB.ŌaԿyUm|qTz sb5V X 0 0ǮɫAY,xM&^xtfYՊb>KӹlkrYPhbrdf{ 'rzXZnlZÑuΫDD1^d86LR+#(af d"*%׉kaWf0gmxQ mgant^O s|0pRV?Nj%a.XP"3 0 0hLRBrl7a<=ݮĘ9$]@:2R\rj\6+s4)8TX,r9J)|оtRɬL*8 __1vr"f#˵L:'YE=06mS gf)ҌFzGear_dR3 0,< |(}Baa0# % 9NŢhájQgrD bl:%Z^WI/d].N+J&Iӑ%#2GDqb\V+XCl6Ԁ:߿˺ ]WN$Tsmjaabt< 01 0 0 0 0 > Eq9pxjzZ DZiIVɃ:}fL&#F1^wtttzzzllvwl.wxxL$ BRi]czvn4LF#TNT0LWJ颢n2=~P%!3a snw;pfP`6!wGs140 | 0 0 0 0 0 0gD0 # GQvSANB8QקVfM&S߯UBl8>8K2T72/S~{*9~MJIMuphghf~{*uQs'~bst'{侘90 s`aaaaaagt:z#ǃ|~kkk{{;HKv=0HVbh2 tͦQQM!4^iZ ëYyC~V2Hi1lPՉOJ0r Q+P=O o>@/TBaTt=ш'Jd:=P8v*dh."` J!)/ dI eo0 |&caaaaaasAnp:`8|RbL&VD8!V*鈃N$8َ X,xaUt::j5-s?D2F'L`Бo sznWTDn"*ڑUPp Πn7uTr\[iDߨZ@S0"oD `D>,f\.܆F'D[(4r*r.BdY:Mf2N͆|>i o<߈Pr9)A8hN"6 7z\.JJ^ccd2YVݎ,VdՐ ]uUfzNk9Qb1/тfZʥb ]V#R<:*t.l6?R)9*A&hjQ2&j6+ժ 95<ã0Qh?8Qjh4FCb6JaKe̛4@c.&">\>(afa33 0 0 0 0 0 0 +B'="N}>ǃ 55t4B*#SՐύݨ$1" *:vVAնZzF,tH&ACtGp$bZF2zjh%`pݘ2 &W/NT0L:].1cTQ`>1Sssr*ҩtI5 aL{<K]Gv^:G$]GtJ~3 0 0{0 0 0 0 0 0 |^(ƌbfGǢR>7SBAcBCfvS!4jw8lVc4Fv/)nM8N 8JH \n7zn2Mvjd6*VW%W6'%x"ԍ NznpծVk8llŢjh4:=="L&u2q 4W f n 2ȗ4.$QXy0xPQb̨d, $ɬP-*A+v-z>ľini ?>>>15lz۱0 0 acaEx^I%J3;HwqGu:2)VVtH'RI̗%PFbmӃ_0 В VaEĐL/"\QԢhQ:SOKq|OҎ?"'Еu~Y \NSS1TI/ _RdEmV-1 0 0 C>(d6SUzn+*['d΄r8~_7 54FVdf2"fhD Ph||x.U+NK=™CQ(mJlV D&''#Ѩ-ʴPf*J%t8ѫ\.hc]5@{('GGGq&b@0Ȣ,*A\%z(@ 4[%cy 9ͨU rGFF&&F"#F*YPx1?.SmLfN'SbQj6Q2MZV(+*cXFN8F1@fa3Ǡ%yNx|h$/u‡P6N:(M(?K5;z`I{P4 j8ٓӑa9-$t)kqzGi?W_ 2R4F*RZkH L9?lhy.beoE%a纡XQmoJ71FCY4;ynQi0 0 0{ڸ>, vSP,.~j6bVmzArR.qQM$ Eȓ ˅RP(K*jWt6  7$h4v MNLLOO~ s9QOh6Yل b0 {J %;ph2H$DчnNrlu(ɔXNl63 ƴ`#z @ gCҔC SQP(J%)S!s`Y-V 2qf2CFx>)Tfru 1YTd<ȱH>0 0a>憵xf`G d2 ĝdD5ԱXGυ qn~>\]^.0`NZy7:aزdI-XN<Na\n:&):\USC0anom[:۬  ϣP8 AR kZ ʳv!X'S|>Xv!Q }+k++[[[hO= Qb B8DFBlT*d0L泹zhnZ~RƜ=}'*yƢQۅzT>44XȔOJ-jiZ* " jd4z<1L`[4 LZPh4J9В |)HdYc4F"BlT|>/YfhtKa&S\7aa>1|ބIt^Y\s&(<1n;w<0;;bX lv:lJ c6i'`>>N~/VV/9~:LJ ;7m\zzssn,hRk {{P9E&a6Ŀh`_պ.~b]^ |kyփZ Uq:# L;=~"9)W\rO?7|;{:gV³2W)^ _]^j6ׯ[MN hzѳO‘lqclGj ~>\.w;jĒb1vł3CsǃhrY8H|n2'ra"Fl+;q !;^O==/V0biѧ^U⎬iI\2@PV2ޠ/XR'bh4D p;n٬jR\Jfm5t\valٰ mvԜf\=88(a"Hj{Yر$g2d|_]Nm,_QCx V񣣬<11 0/81 ݄ xrjjzf&g/+ö""HÚs<׺!*<.)H#Ւ6|!z6!/tm~ZTDħO<:jw8\ѣkׯ'S)r?!ER-O(.W*4X>hN%ܨʋaaa>>13} F>KlZZ@rbhzd4 := :NgQRN'V7i`V+V©Pj]Á5 nǂoN%M&StP(ʊo(,,fӊ½ѱ1DZK$GGG.sz KV%19h%c*2Y,73 ndHƢhK&3JᄢvRԃax0vj@a::"$NǨfkjmX*?:ظnWdwK%ü6MjVLL0(2IljLEYS+ժ+PV|ؾbV2Z*,+W"vQWWS0ju{kt.s<=]!sNd#acH'eҍz*94Vh;3ՙk17RT2 =l4Z7.о("P(lmnnooc: I!g:U}qG%vgݥYa.cx>_فeZ `0^#f:~׼;SdNף8eXB(Cw=BxarC<@9[WX~:ƑH8|hv\,w4N䭍&9ll| pi6a%J2 0 0`xWKt2Oz6:3=yf[~٢.>4⎭f%:N'E't=+q8VUUTޓ15-VfY(Ţ9\[wdꋄVrd)?aӉe2Ã#\& e2VȀ%/.al`PvMB*lG9  h{S.WF(EMVYXfb w".b0ݎò+b^\HVX,qaؖ9s0 0̧=f5MXL$B\"rdҊ<>(\#}P`hH6a&ng4 ðK^欼Yr0`vk3tVoGI666jNÕVºWP0QG+*JFQX#GӇ0saaàu8T, LjP _8AXl###|>HiZ-C bqks3#U+D񨤰`i ^wf~Ç~qtl,NommA.h78$jѱQh5d" 1fbV:>#Bjmۓ7Aihf9` j`([|D|xpO$ڙ:`)f)͞pdrY:) xu8zq 'i&t r6H@1r2g؍ҁ2`"җ--Z-~4caaa>XltLK҆Pt23/X;(+o.V: 9͠t؉gP_)oqv{/oJI-URUe,*j:eJ*oZM8F'*1Xީ*KǃZu:N'c#4Y Tny>bfEiɠFwu:](-qe* QA.8"MyJ&O($09eoR)LF<\\0aa>!1|$}#rjzzfffg{T*qp:$0 vw;X2'&&=ɠ`(t7n~b(6d=~r=y$0`օMYaΑhny8!#ޣtbttj---Ci< faw0M2HLp^ _ٰa^~}~~v;f:؀ŝH$ЁkׯGQ}i 'ɕ\.G70^ NQCQr:oݾ=;; -uYqO7:]B~:tLݛ7o,VkA?$dy͛׮]Cэfp-kףcG/Hdui 5 NI<|?/XŬVի7nތbBfs9ܺu Z]V2/?Fѭt:?)̶% RX.'޽ msssj!tѱ1s J 6{5 N IzJ!&;;;O?DmƠ9 M>%˗/*i B ֝FA+ΫW.{l|z;Qx-#’߃yfN0 H͇n߽;77\L/vv553sUߐ8SK s%aX,6|%yL6 z{B-2 0 0 띾/Z-ܥKL3d1HtZ4ɤ9ԕ^ :uK#&ceJ2lI`SkʦTʕ Vvzl2 j&ŀQB'=y/Ci) XLV&R(#sNes>?npqet_yS# by&"HǚcϛADvW~b=(KZ-EvP(C,ݽw!zF޿/_H3 |@RFb@ N[TP+o?"V-llb (&'֩i $H~ttXolly7Zsr.I&P7n<PNF "C$2>=3}ʕd<^ : 𸠙ʽ=7.zXm69K@iqDd;<:quh8T_8`f—qv6|>3 si1 C굫@`{kfV"jvY8y7x+˙$PK! PךbWaVU 2ʸ\E;\.k].E[bykn$B$gFӧOaΡ>gA~_x!ʑ[VA/?饊Ŵ s͎c.9AztD;H=49>碖^+EACn[6ww|hFoS1Ain| ʼ}.  A^2V'o{(,V랕aaO{0FkuNT2tsssRiue%LLMPzOFF_vD<pjzZd/ N,%N4ǎ$m7 ssя?=qEVx i2pl KCT!/BLi/jegDmaajks6%/LbK XlbrRH4x*dU0^~ 9-;u:*C.^( Njo$~fvqؗV.54 u]d o^.Cmۦ N@+$لH!:9Ό. Ȩ j DsxŋevXnIzp(tqv6)}3Tؕ%CЖ2^&;i ۻ9aax`<'q9fac9 jXGHqP eԣrm\Ϗ};x^xJbc2"YEŠIv?8HǠU"GDiPjuA}BmF#d3b>FۨN/Q'ۜQ1oRk:6 cB/BQL8 :HQ䲔 a A#CRb9?&ǃ V5r'M3Vyaaa~0776<^tPXHX,'p(>`mX\n7,B^$"j}S6&Zf ,T?CXd=mzarrrzv޽{M+ ]VљJ&cF.:+ݎbK!_KtɁըeDT GGG׿.yvau՝fڢL00߁x >W6נ'ŀ8?-$F:r9N7J aq{P &4¢1_)FtVhTZ/mvԪt:?}6}R<KgldTL[+Xz& ݾs'ܼ};L&sBXU+ѓ^/f)Z썞Cx YYZzY:st.@ E lvmuMOOߺ}{rj|R.;].C"@2Ɔ÷t 9w\:!A$6,uVb|XhWX15VEm2-X>'wQLG5ɠAI] Jx/Bnuv-r@%78xtbrtL%ŀauivodf%"L:h6ׁ~[}baa柱D304V`0l`(Df(:N%!_E8:+#teO*S[Qx>sjn:C$t+S̀bXjaj[-o ^¨uTUTd.}rbc)4veè׫V- Κ͐2FtjQHv>c- zX(h(`662fP>Pfj%hvtlllddp=V n'c,; 0 Yaw¼, [c?01a-ollmmBgxo˄/ ] g&XUUK7odaRl1RxNRJ*BcWgfg67q=p["CѨi[k}Tؽvڃa@oZ[o^d2<͍ jŽ33 -g/;Ko4d0[%6/OHZ^X $.BX$, ZnB N_ޠfYF^ӎM.QOZrff FX,Ck lt]?VwvD]m[}% 5 |>^B!5'\~hv,H?/htrr2eH?A&RTfE&!ڸ=r' դw\Rz- .VK?g㒲PQ=saJv9@Y(bB Űi;2P/KE~犜62 0 0ױD30r*b$p8fmߥ2rqiFj4Fb:eZ,("ܨ !{-O+Kj\*ay#6t&XwZMBnKKzJ JC. ALPr rcZPf(,2 6[u$D`(A28PA`O_sq֠V[,Tr6ި9͠BLjQID6E[r*ĥ.w.`}[ [0-XSXb>1iB(đ( BX֡U3I V#b1Z3roz|ydaalFVeXvv ZrҤm#Ơ wz<,JxX,V'ɾClU|ZĒʇgEVnZt*V%b iVZX(ۍ$j N E|>_jX?0Xh؉G*j5ѩn.ەt8FFFF(䦖2Zlj F)djZhs gL&%8ɸ5b"ydp8(Оpjth[j/ru:]^rzFK@[j-lo)MpZ-B+Շ pE 0 0 |ŝ*V+2fv>,6`ev{:¢ZZ-rɃdsv=^td+4C7:?Dže” tD2"IqJ%*yF\.VZz6E%x`hp'%DTNT h };!{؃`3nWK~6z=jZ/#ߴ)̌I \ b[,p.&jqh4V +M,?) ƈQQ$.CjKSb`@cQ|Ft/ƞd0dTrVddŰa.Bz"0qh]y:7_aa.1|ϟ˘^p 4͂t9SST*ٹ9"9a s/b/]Clիb77a](J,Z#l⹹9O  svX@0(|{="Hd2'hƭ[|ͫW0aGF`g!daT6#~X aqVHI>Z/&vtT7r<j4SSW]Te2@ pMǿ:?NLL\v T2թ.XBwBH&:y>V( I,Co~͛5酃!\CfbqmeE\t\n[ M0vvvv=^ @ rtF歭ÃD7QcJH$r zPOLRd4n van$/%o؇A-735C(SJ>5= Sڍh*j60YdsK-fA'-ٙ3Q.'1'q3 0 0 wT}ʦe^@X`Mu:~_)z?nirJN܄j뽺4z厎Y^ylVzZΌw Kj~V+ZZMvڮGA,s%O5bj[ZzD sbnXې, FAȋ[rG `3|ZЅLCɺ-޸\pxtl#D"J&cXaٰB7Y,Z& BxV& L H$2::)  DtXq(ɡCvPA< ׁX{ҽWc̨ѵwaacHka͓ #0bnDar:L=X;;s1LT '&'aգc< D?DFF`JeGXl]]]MR0@EF:3l:K}xu+yg{[xrOL /䦎k7nONR.'C,{9&'-yE4_=zW/_W}07?ٝ)( 4r(f!,^/dO3bzՋՆ *WRC߼ys+8%1TjaM{͙BƦj >xDZ& 猪>;;872}heyyowRAX)}L:sܽwyE^40FȌ;L:nw8"mon&͍ xss? ,fZ-ɔej0#,,\zڵtQ6IL4I|+ΤERZ/=x(Efj)1zӭNBzxpws27G:ŻMi:6IO8XT׃BxaNOO;Օث"PUʠDJkt:ԀI¾mJqR BN GE0X2xժ}aa I3whk xc^@AVV%ΪJ NkU7?41(Pab1]:NXZl6iJnke`nhq:)IfNKtI`݂"JY,V+ƅF)8N, qwi&aF`!zauȅ$+ψ12T<OqɄ$r:{2G+Ғ2Cю\KQTz}(RcJ[|%o1Ƀ}F1n隃Au5TaX։D]3I/G,|*x~kE6Wr ^V <2 0e=f+oU2wo(S,RGC!pO)$~“量;:8@U>&̫[[ZP8|a c尶~]*mv; O+| 0œenF0}).H##2L[tf=ѱ)ظ8W5j[=xtp:&mg{8VA*0:1ȩ|f1(!zYߨ Gh(!,w:OKA;ZM%DW6})\Y\ϋr˗ۡ`"v߼ڪP`(4&&vww`tB* LLN9ǡ^D^Ac!m ^ge~C4xǟ?{f4gfgҙ`a. jA ~t÷ŋd284 #nrzDtzoJftz+J8it__Ek5a 2Z'C^7AmS\&#ZmwggktttpH`eej,4NН0de 3b+Ŵlmm5MzZKI+2ʄiđa"\ ?_ (%NRԂ@b6xH+#tJM.jyD= =thL&Qzؠ~ IIo <-sh#\*)ghlR(MEije%ң"}z\ a6p: K׽t:;cJUrQytxOıh(Ǝnc1$c8NbhJ8.f}CK43.Nbvaazc`./NY\>lV. umu5H^Aɛ~cc6L@=aF;,JҜM'?d6Úgdjuii f1lJUF\By,C`|YPЅbaJ_rٳgXAhkl #64:3&/ښj~qGBBb|.D|JRaoUBh4 xgNBӧ{{iy ͫWXQ8Tk8e͛B>O‚9ݿphݦ FH"(IK}݈,ʐe3)A: 9E o[JpHVX{}>ɤuS~>q\:]*Q-暍FRy@ ¯?O$(FKůi;ߗ[!ΙaG!0ؠ9V+ԠQC)RKġ[[[NKi4D# 4=&*m`/ FUuossB JB fJZ`B')ʠo \HN 0Nnt/Ha.jϿd4q>BnmoWUncv:0 q:zFDBɎ*r05[[9X[Y) [}"?B* ^5ڎqQYfaa/ȋE$]Tғ ,%&,Cҗ{ &}6ُ TeE&m0jYxy(UO)_(jF%X:ՕШwATmK@0v Q:nml ͦIr\C.(t j NQT|>@Mr(&]e",s04mf޳bUpy` 1D)1JD~)#PPy )sRxQNtT*Dg2q4RUCkQLE:U)_Jh`͈)𐢓-!f`2\|pVvu!ZQ*q1HKqe>6v]f.q0 0_m6|occ דj`40dnC-g-zOCGQz0=E|s( WwiXYtu+ϴ҂DtdD %o cWD*QXrPiqkZdiÇӟз<{l]ãGǂ˗Fґ*N_dX2_5լMDKITAUlZԑ%ԡRO(}.( :OXHi-D,Wʦ䚂ZJ  :rgBz2K(+qǴZ1"і5:9ȾΠ2fG B@[$om!1!WolˍaASR P`W`*F*t`Lyrzv'}@t)ON MkL]sqo@ ҝa0 󭚔"`0h%nxl|\ z#`drO$tÒSS###V!SD%T*UT'w L>AvdqZ}4Rfә zV^'OK0&D^[QKO,O2S$FEGF 7 jJ+,1(YRA1 T*it5`x4Ls+[G-r9+}}8&|N켩D6FC 7>&LA{p.j`&S)I㧁`EJ&X*c1P0(Cth#KLKH$NCͿ.\6k? ،0<߷ǩ~cFGo2W(0 |Ͱ {|4*? 2uu: TCS.oޠ9-gߧ]П|11>tjiY}3E~U?{%9n@ޥ7{H7=9ۿz߮FT.}xohdF,יQ x _{n'n~|;=YydrfwZXS(O>tum̈́AA!ܴ4'O<'ƎE^T̼3oӊEgoua9c36<\-9,k|`W|^b#ڮUpzx-ꋎl_G]TbAAA{dN5LKdkL15 !9z8%2ϷgS7FnDI$L&dј,R{Ζ0q[2sR6S]H>3yS8wdJ9i^:G=ۂGnӎtV&!;{͊4N r% BȌ'>9ҋ%2GdX!~\@@sgn@b_L;0eH[LWy`Ɏ Y0Eh4B_;$ b# fgL'@WlwWJĖ2[%:x?[":8jJOL|.L84KOY9Ӫ~9} vbA^Ͻ h8buhg AAAAA$Z3֋\c-8Ś:9M,@jO*zٚlxl?􈟒#;=H#'&t$1Y7<##0^[fyF댷8"əJC5(b>q]8#bq2rA ym4{E[D      )2c:_9CN띞/*>/ͯg?'?sHF/څs6d&KW0u8R+υ)D I^6gk&uбhS rA {uAAAAAy!H~ v ^=F=\ϳ/4Y]X'GAAxW      LE-of~A䪁AAAAAAy g^ZAB AAAAAAAAcAAAAAAAu:AACAAAAAAAA*fAAAAAAAAAAAAAAAAA~_bAAAAAAAA}ac )(_6w z' KP(PAAAAAA.5r A O!OAWY@AAAAAc!!kת (}2/؅뺎(}ś ղ!Je}}=J&[`= eE6 WUÝnky/? z]σ @     3eE}?}[՚ot׼ΛdWV]~^~W 7oݚ.1DJP0Uյ6cϟ8W3 MY6}B,|nQs0PmBCuFd     *fK"˽{qRL&nw4jXM$/IJuu)(2,--Æ|Ï>`%tێ3P1 %0BϿbaaa{k2&GO0hG'50~a`*kזGX~mZ-W*~G477Q޹s;w(!7nz 0 rP 0'dy`lsjzx4\w8m؀P0q0VO^nkkkuu!XI5`?|h4`sx08|?>x }z&zpi ƨz _}#OL8`WVW ,mɧ8y`a z`р"     A. B۶WV>⋥ݽ{MvFHT)%/66cZ޽뵚y|W_9{n׻~:D*5fѨV=|(- loonaw|_|~өTA~[XX׫{{wuum [E`?1Ģ֬ Z}p8}G`?u0Çۭڵkׯ_uf3qQ={l~ryӁ`KI}t`EG [aD*$'R{MF# 8>3|߇!7oz|?yeuuooΝ;꧟>~:Qaa~~Lh~;w:m0Wxk{nju'16     ȫR`ܴT?kz~j7}cl4C+?_(vvKR.b>O?N\ K׮]ۃťvɓ_tJemmm8lo߻{VZ,..&sss?~1u]fۓX'}'ؽO>֭[ngZ]ذm{< CPWVe믵Zh s?(Nz=/,.xO?j CXzow޽{۰r +aOOfO Oݼy>0\q&4}~_\+ ?{F/͂q߸ǕFݿ{1 C/ruu^Ƕ!(G|0|Bu#i&W߿/(JA @     bH)KG|֭vÇ|s(6S)ǶGnݮT*RiiȩV޿Ҳ`Rp8O&A2s>N/Tƽe1bA!DXo߾= ׿ܿo`J0bG3`leuL_u,JESiY`=ah9~m +Bΐr ƘB~ZXa A     b1T*/<0feu5l6Jfs0'ιP9d1 NGj3R)FQm\yL&c57f2?G(;ؔ} өXť]L\&_\+ s8Q{a:nفB.\_(-b RE6= |uj̠)-LW0/0O<ɓ>/03 obE4`B}ׯ;Uʕ P,an po_1}e$RԟW%.p4 aȚ2 j7zj? d<3(jW_ U*plGAAAAA3oyO0, =[]YBJC vĶkVW|:fmR(׋bijGvv7JǁgJUCAϫjV K@䷲`977U,`?-// )}'2ɤR)0}~+W*ׯV~%;|~m}R.{Zv?]3; y`'/6lomj_]3z=n~ݝU m$K͛76 vX]38Ly6a`00Cl.>;??U[AqVW[ &pqhwC.B [l ѯȪT.*lRAAAAA䂠bl4,Q񧟦3闟~Qߟъ͔JBy_|z>7W_mo>$|eҁVWV|?}WZ/M(P(~_u\~ݻwnܼxƍV],0"mZO>m:f2<0Yg`>F!v    >F㒃R|O 0T> FfAu|h80$ ~a68gkZ`? y~tKRl'l{vڝB1?^[, sBod|!Xx43o;?Æ .;ӁAD703ZRR*te&ʸ$4^VL(ݟ~ٳa}}>۷`D ;?mon\*9cN~'0(ΝMރ8B&>᫲b!    VK_$C;YǎT|Zh4!̦w]8p-//gs^Zդ`G<^/[tj0lmmcUƄw*O W(]k*dm_؀w=< V^M !c۩ؽؽ|)=zt_y=yu܏?dqiinnLϟF4H.6``@kku4=xljABU&)#λfF es,- Vs@6 7?L+B( 7nܸy?Tkܻ{w0*;߸Q`0  k1fT*W*`= ioVAAAAAc2 b&~")bF݌Y4CZz뺂Kx>\6vRɤ!%;~'X @&|~nnFf@l.7:P@&rRhGT c۪fkϞ>Va;iðju]séٚ܂ Wg?qO&{{rtoN`)!`@{lgkٶ-DQ2vcf~O5^?b?YU;=F;;h?*cu &Ա`C(27H ` o}:~rw(zdj4`Tͦz$+$ ru^NO˓,qp}vʫR!GZ2AAA`n(eS֑J/q|l xgze T ;h'b.8RTPp]7 C_R>Z2z5=񧹙5&ד Id. r%ceV/< ȟL `qXCc?Q%3-G3,kDOAjm۹|>*>XPoSkX3L4u`t>2EA:rf\ 7drAI>Op+\3zA櫈f   r⍓?T2Ʀv$tll;K)A ry9cV_FZE8' q 豝. 4+ 밴!ӟaN'G ;6Bz盛N+z!w>4{ \EGx<. Md2D sn;6ZQAp+~Ph;뺎L"08(0K<σ|T(`2GQd¤^ Mg"Nq )M$\+c<u&P{&H"^AAA,/\Yxg7he Eb]=P1\UB@A 6~c( rw(u=/\.fűmEep8z`2$qbXT el2w:V9bJ`JlNg2sssr9NC^Ղ#2ڡd.T\R,RU2i6[bt3   2%v0CŊ}^Y7:TiFqn]Q1\(A~"EA1N|RYXX(͕Jl6 +]1cCO&ɤuFl4:Pf x7??ƍ%qzv~AD$jdmmm50 kPƻsڱ2u[,ml@>B <3AAAw}SDf|L?7 f]~^Uo*fAAAA5FNRenX,Jl۞>1h3`0hEB1uR~ ؼhaNg*DeJ3pP!7o\\\>]o6ø*(f۷!j/JT   'X)LdF.cA]*A    fǎdsŅյJ\Ǒ$p*q2LP(\KW_h eu\>_A0fՁJ%OpryC|×]6X;rV9x"rјQ?8S*oݾ}֭EXڪ7~~ĹRjv*.Jd2a6[` 4 ND/dS@䈒#Y>+cϖ6˜xL_O6ˀLݞV'GJ;|ֹ AAA7];o^7pf(sP!.BȿOu3+W3$͌8gPw{ K1Na؊7'e֗SK>!_'O x( 'Z*+rk׮mll3?^Vd29hɶt: L: yn;8i VC5^+9q1$ sGd~e k$VKع   6}̐=!U> N}da4hG7XC!ByEyoоL3Zb#W$2]T뾥&!9Flze 吋1%!!g| + m$o%).`&]d_KA%1}s~zm'P' ;raɫO+V8eNdIk$"CpuTT.-,,wkvvv:.spW lmaO۞Yd<6똳c'õV'u؋}IzOf+1s6+=-PI;FdyL謓#zS*U 8c 9sz@o@N57RzR3+%Oa?\cIS5O^xqAAAy;3->XxKSff߳]dV ˉjnYc!\thG/HʳË;77R*]Ǚ}zH) |5+ssBZjNFK?зKb:VSI'DHmy,˵j:/tV+ڤ0R ,'VSʉ~$C$cCl6K͌i<&^ ZͶmB×8[:-3ZiR8,kHBEdjlVVW2٬p/&X^ WD=;5QR2q,$M`wһ&XB q![An<@ń nQHqRHi&3L36m0Ptdz~ˋ*k`s@bz6Ag2KwaՃP(TI #:j$1 {W輦1FǔiW Lە$t L8Q̓   F~6hWƹv)l&d?,l23rMॵ9!Ӝ͘JRq :;k;NZ7a{ߏU|t\Ataq?l.>f~Ϟ>8 _[_{ofw{|>o͛7?~ "}^!V) Ѐe-+(#gJ+3fsZ& nggZV3ka,4o{2mg3r,K%<<1dx42jYd.(rL6 Le7ڛ *r~'ܻMU3L:3QZ%4')g4 n'8LΎ$>,8iR kXZVu3+Q ˁ ׅmc F_HlN+KI^,ZHD +l G4 ̶aJ/wPp\l6N3J04mQ*X;ORFR*@ ^^p8yw4z=UFa+P5rn4;}1#QR \ש.!č1   țɼ=2i1\rtv4 Q*cM"O{sPaj4?/?ۊhT*Ey0ǣGC7ͯn@8ûP19 >NֱuBaEA0`Ȁ'9[ee)]r usb#!^l6 70?$ itڥcf]3wdofY-vQgF(sb(KH͆\vWbҗjꑾC\dlCф"=ӹbFA$o_lc92 x,0:p{ӜB!{8pcH* 煒ɢd s2.J؋vL4,,0L @sbyEI7766VWW+  HiI*G6Fc7K@&KZqڵť%%~|S*Ѡ. =NPe<퍆C\.a'˰L AAAc^{sk <܎r oL2̋~p5%epI=C$V"yF}VU8fG;<#rBm GO̞Df"%q֋D'R7#iH됏3-a>P) !mhК!FL?ZK/\Ny=(O-rĔ\f c%[T<{N$Jyq>LqƑfγ#_q49^; P:|R./.-Jed4z$Q7!7Ht RB\.t̻t:miov3ʤoZY]f!vHXV"ف~繊8;A'])=P>]^Zmlv{wGl4Pi˸Bz([`IR{X_XX\X֖uZLJ?7*Jkkn޸~ =So8vtb6gb{$zY9aR-9E/f4=p7f:U3Z`D5L ER/aS Lh8ԗf8˂t֋TnjMH&9Flkoq2'k"dQqu٩CI__mq$cl;n^ 3LJ27Xպh8PP(,//+O'++lv4흭N9)ctR.]RKKKtnd2|l~mccmm `[~vPB|.QFGd&:ՉplmC677=z G: z2|FE ;Q4^g8ŵ t&?_ord2jׯCC@Z Ga.sdG[cZUR븰ťV>lVV$#uӧO67ͦq6>AAAwŌԱBm3ӮhZ<\),*:G̛x|"MIfˮSy޷Qˢz>K6bKOg2\αm+ȼm7kS\.W瓨"\ lIQS*o$qٶoɜ!cTjD_(Ѭ VnűHYUPP>fHQ':]Ia[hN`CU_b,y/ '%4;wb9H3xZz>yNt|+F4Ӧ $R{<Z'Q]b1D_C}os@Un֌Svvv?{j6Wy^ Sԁrr;1啕e5\9i[mp<߇o7ZX\\ZZ <777??_7*ss+W,+俵n3p(d2tv(;YǢJ ښR,WHnom6aL%$V +L_׷wP!~9{t ;!Á,,,@AZn\@]-..B{^mӧ[[[J3B̶=ׅCҦu-%Y^va]멟d\v堺VWW'zkz`oooolg   *PSQ$8?+:2V \s8uְv5$$hҼ'.L;D[ɘߵo~; Ϟ>}AB 2P,~Wbqh0ۃnvwgξithӲ>Pfc*I833*\]2&)j\ZB &T*?p8 {p&P]B>6 ӱ4ݍ-h^vFQjvC%K%h!M6UM)sm1 ~h./BQ‚04ѓr BT*ykyT rYyA@ajVk7xQ\ł@kzvd@ruU`m Up{k s4Fqb\Afhw:@pڶ YOK9zG{IfB!͙9 mZܠna28LCVl&5Y, Cpj4j/Z47W'R* oooC>GF(FAAy+bS 'e}iO F+]ʨ~axgl~h>z'/9``駫LȻQ߽G~lkYEzNxCp*Vۻ$<޶btI >r2q3SK`S70jbF cV!UOnI)T(jLxQj޽^oڗ°g(θ L&{{iIyd  XS(<K>_\\TM!l+|Uʤ5   A ()16eƲ-QzbT @H_$J)j޽O?mǝBpw"5F7wjUHd RA0ўy 9"*BM=ȃ7gRRC./y!_D+h U~聏|BSP2)GjE|"$4G%;r )5!tfw}רՎ%7O:2ag{ᅦϤχQ̗6F,0nK&o p5! SцQlh!~1f]}SIc,ٶmn6&xP(_zԌ 1y& '(!:ܦ5j`map0F\bKD-u ذGȧT,+JRT`eIq03|eU*ΓODQᥴ~P!Mv]״$Pt 7%D2m\j;{{*j \y&M^oZШ   @ Jr&a]rm+v3Lf8z+OwGB t +zw;z1="'s> f^BI;3;eɖ"k\cYF>388pBmE3JP!xKid)Me 7$ICN"PՄ#7B@-\*a"[]vH]]|)\uj(R}ѨjfzKA+ s8*\fNgg_&Hq YK:9b(qۈ>fB߳)͉e3H*1yCYw&$M PEM31&Pκ& VC;qšG~a4Hr!It&U~(ϛRX*B]eu)Xљiu2[!MMF8ь >xl(r#Y-n?ly7lAihTgP'^*zXaGe6ǓAAA-AF()_ l6E.:%Z$\AdG9zCKx=0Zom|' '7ewİG@5:].))/5\Q?J%7.P{\߼|MO$j&ƈE@1Aeǟ4LGұ,Z̨/26=i&׃)\]v1L4 JIARpDz*]Zċ8l4E6Դ=XH93?8uL&_=q?K{[R87ce;0ꄼPJ*͈Yf|-nfP5;}he#YITG3f&=Br4$A(%e1v2y4c%#-y3X=Xrއ[F cŌ0Z. lD8]'jx,cin.W-rtDtD1sF_B+a?ə9~ =r8σu "W"/DEq/qA4P:`0v]ERP(tm{&H^X뻾:$m.Oht#A0v^:ƚj8mF8עĢ䫱3#ZSx 5ɆS4[X~uzrv2avK5si0A,Ѳ$d:D#f.4u|/(` Opoӄ2@   [3 1)Jbcg5"nB)!Q#佈Opo ᜭ-=z$&ߩKౠ~X(>CH7a+E^n0$i 딙Lڵk_~kfB 1:x4}n 8,pXJ駪($a0p酼h,i).!&шP^@1#OZs^s{pQ?,I鷞-q KCjٝJbڎx"`">#L+ÈGrjAaMp"RGKd4g5b LԦ,9]"8+9l6ؐڄTס/q:1J}KS)?9 yv r0;%8a-rܦ%ۀ BdF'Fvl/,K%/.- h#f݊ʰu7hKx bL!dkYO00xZB":-'15@&癰Dɀ[Bv7Ri6$KEȪX,WAj% Np~|y; ia*R BU;Pυ{^fG"g30V<?L *X.///ÕnfaMۅt'L*)  {$bo7#*fA~ Kc\ՑVěaV'Pw9j$)Z!G&Pp! ξwƵqյbG%}Ǡ|b|CP+VV&Bzߏpx|7.;A'βcgg"e-6'a-,Ņ/T_A̎n|)R OXek*飩4n?~,!uhm8@A)6D.I%aI9fg#=l봅#,*ɼ@ԚZ vG/P)]/\.*ѡVpK}7b Bo2/0VΏ?_CrGe["$ [~zp!Ό fYV4O&F1ciO3I!۶=KpHGlT0$dkf $VfLP$3Fҿ)o: ZTSXD8k!+? g;v&]Y]KN-R]]_#19# ѥ=M^ OUUtuzC8*O%,"lnJRyPo` Tdi_YYX'(9T8R@ϯ~ ta9D   o T H4Ypآc9vQ7pD+I9D{A ITtdY5IP%/P&^j|Dr-Ӏ4/ ~mZMp wvv6cu= 6lZ1r`ZzӧOǣuR/|.7(NQ('B42"a1ҪFʜ$>B)Ö '+R&iRRhb:P(T$ *7.6Q2ʌLጉ m˴TC3o (<( ZSaD JXm:[V[R(EKfKSvp6aVO?!}XkNKv }s5}eNKrez_hlmm <"#Eq~^uLǞ`K*5 GP2Ywޢ\AC4136ݹBP-0w4`V#mnK*JT*Ҟ5!O# r%b&`,4L6 \\\NG?ѤZȴv?n@(`81愀Җ4|'Z0+d aYկ"h坝mi0Ñ^󫫫J`FWMqm$y۶#O"ZdbLF F#(!3߇ 'DzFc". ~`kuKR.]vm2Sag@-QjDmKm.  {WKʷcyj3 ,_K%y񤴎he y˅/oK'3a=>(*P LjuUHv8pl29Lܿiá1~<O?{.P!}؜ BUjٶT'h|Ov{߯'>Fz[QDWӽf`8\-:v">Yo.tPB+WT$3Oe?i*Y #*\N9$#$ϔj7 2JLg+ERNt6[1P}(=$Net<&i1\qxNU[ ]B(#ej=A{:"H)3(}Ƙht?OKz^jxJ_BKe?6? uJnݿw,-9SOgZD&Þ$ i,#ٌpޓdH>yAKqչW*bd{蛗}0J\GIDl j`Fv: XkT//Hi\\Z*+kkPW&obNAAA [猯7/sǗN3AwbgM #s.+0L8%.A$a뇝PyেesޖƎtؑ@s>3o^NzGPMn\vRckwN;2AKmɑ|\TUdMaBfFc1g#$LMIVXiwy:ˉKkn(qr%sLh$Sq>RdP0G*`'ES0%Gg܅pé]|缰t"SV29#*Ft^RIR6#1;QV,x AN4͝|>тL: r #/`C\y^._J2jB'3ёΎUwbb n^C9Wu0A6sNs'ϗKl6!v:jv)!I3Zވ+&`5 YEQ֨i6lLcB~.Ńz+ |m FǣBAHU&qX*-,,Ra \ (8NzJ"`h4jp ׁ ภPJrƍ<LتlB 3k+Кav \>\ԔJ] %AAAA ȻT&^9YqE.lO7% kr0LgU`yCn!nrrV3/GWƜpQvGzn^5)́Ȗ1584xL`&SJ:Sp?|x|Beػzz98 cy ^t3'o[]y9gjqByFoHdfV1g1](I3ȘGM$SlTc2feflYD" 9V:aRLRg6C!\O3r$d1əe--9\E@f$2pc Eҙ_ VZv<0G4V "Q`yowjvJ4S@aǮDTWt   ȥ$y;.e'Mϼpnë́Aw"HE'Hf6sM3Q*~$~0!N.6SR5p)X80j'Y`# -(l$Gi$+H_Y!{1]DV<vP*V_TҲ3C!>uz!6d?dy@ WT=r♋ 12p840~]X\ fsl6kj( ^өV;۵ju0@JFyX C[.IX*I0 9$6Kbf2mqx2Q-W*L&2b1! }GGql4׵B,/-4tT.+::ԉhc(V@De}zh;[#3N.<ǣJ[A %d2+*n+S(!1,@Z6yB>f1&S}l\*|`+# BTDZ l6Pb477WT<σ".1^ŔAO( W k'L  ^ Rs7MffO0@ p+kzAeǼ=Fۓ\5 ȕRHL9EXNIQԣ͈HfYUR&X2'c%r$ĐPOR#Uh Abq.  rEIZZ43^jWWKbZ1RO J.jjjl4ݮ $)CXl6]ׅd ԁfnKBnC;p42~Po8w(`8\^^.˹\1%4( JnCvwaH@`#* J Jy% @[o4vvTx<2to:SMO}QAlA F&-'Ád2Wu2A:6|%B`dŀ/Fy#d5m\!^looC3AVP&}t:ϟ?@LQA}s9OQe0$NUI<AAy-֌RWD-77˙סeuL'R A ȕ\U\B*[u%3Pr$K0v'u g]Pm2r̲5 |`Q0Fgr'%5F 3֞}!|S ~ čaRBSL։+ ! ⧄F4cJ4nW*Y :Yx8t:VSna2e6 W(fwmAV97 CHRD4#Oݞ+Ji-}h@&0 J%ed01s9uVoZ LH#(?,tZ-rL(қV~?ɌG#_,j4Pz}2J6b2rU3qW r9zpn)%tX`׍zj2Q VꜝH0ᜒp]$ڂQ3=X͉אi|%dJ5q\ZFO@WEF9S#Ñ3VM(\S51vVAoLdA0F6MӞ)~ȵ6e<`0(2-FjR !Y3ѤЫW%2OV(SjvP<\t!"%FaQ^H.ۺDjK,~bd(f/s'>Ị{qNA")!C))@xPێq㇁P&FbF<AKXQb3~L*짯WM6x뢼% >& <}'ec-M:dP`S] iOBA7ĉhǒ0՘;*<^ڨgU^”Yf1RHx>rx@'d&Fq0B;fh#d6i8lM4ǢF3%<({>S$sLَTGXV]2d"Gsd]$NvwitbYesT   ȻMvIߖҙ{R9cU37㣏AGrur))tsV\#1Y2&fnL#1W+%ʱMZeqtoO6H ߽fi{%ExldC뚷*Q|2(Ǟr"ȥxv&Ii2+{oCBzT]+e %yF\MQbnH2z= d}8DbқÙ/Ii2N21cD0fI$2Gdffׅks>M!I!O8WǮW+AAAb}%~qX֑'o^b楐KɀA+ Q^%Ȓmy{ ru#G~ wMH2yje(s\N* ѷE#H 8m\ם}#ܶ`p_^wMHB2}e<;ލ\b`8H)~_|3N;?ݹ 7VW̨L=WL=(QiC~N3Je'd?5?D|0Zix"N9Cp'eJ++X$KLj7t B#Q IPթY^Y/?|02t0[ϟ|YXd}qPH&Fރ=} f[V&y⋵5q #۷o?q$wgz}>x(٢COy_Y]KFqLA XΟ4:ѤgAH}~&rppv3v7JhNgu6$2{   oؼ+ޱr9#?9'/$.TM(( 3 ȫ#,5[S`lck3.=eֆRv"{AԉD8]D2g%Y'(QUF%ӽPO>t}}-6m7;\\Zd׮]Af#V7߿wo4D[@4q=7rUv5+-ΉGv{i2V%"qz,bV^+D.'q3CQ^<}&
[ZYuv@fN`beu Z-+0|7n܀/[uYWee-+%-mλJcQ<y=v\Nxa9#lb:d})/|Cf$~e>E $˳i$))/T0AAA׀Q7g4Q fh6IB'e.sYT \Vl:+!+sہoM r)wavd*.!'_/ a@D28+Zt:ˣo~j^sun?߿GQ>߸~Ï>/a+i3 tpHFe–)?"d%'V!'ƣQD@Lb\˂ĵFr?s τ y˙į%?#üDoAAA^Xbt-w_xFeJ\T \ V9cdaDb2B6hKy jepU.Owf3b噚mH|/E`0xp?L,k9C 4|$kۏ?Hy7o^QC>>pD)i͈c D 84Q3iB GA-+ (}K;UR@w'B)/" #NBBË2RRRI2j #r5+QNJ2\J4CBӔozh.\rP}*5ge^GЉK5_2_.?K$ ?cV3gOs0Vc~hh/T!YXU_kkk<W Є"0:ν{~'10Aㄢ$GIO&>~>[o?C1O0H0oNĨ}ٚk_ي+'B":{     Z.'AL(U묹Nѡvv}\fE-vD#!ψt2@iF.+9%ʯFъM{3BEѨ?LF#2̓_AOt㹹nJT*qymL?˦ 05ۚG)XF4@x))h<^c'd$@'N7o€an~K,8qe-3Z)2UˈAAAAzıH]:T \˓TH}!Ì{L uF ]OJ6SJ}@hZǕPUNM;) ~o3m3-A+r~ zx2O; )y Vϐi&b[I@R(j8<,),ò ZOE>} ݞJaO$RSe$E<*u[뽨C0B̙H7%BX1/D{m(P |pNe텥mayh5tZH[~wߍ8 Qbyaq/}p8izQ0g)1=·\2jyW9DψñKtd+:(YYfE5ʻL]ST()tѱ`33LɁg3QkBW:cL  ۏ=VƵucaA(x]{ϑBpAUuW3Q?)MiFq1¨6L\jp=:QBEYU% >Hs.X$U4a@Qv67=|aQ8PHJ!w}WU?xϾB(>qO:0Z\]BfUhߪ'75U)*SPJfᔇkBAAAA>G1S66[Uׁ(i= \b0wIPxx&r(MQjsԞ1 gU1%pi 芩 s]BDajĩ̴$š`kk??[f2Og5GCfC+Ehi1J+ ",Fj>ql8P)JF4#-]Z>FaLt'zTu?{v_vCοO?_p.0mRv, \@T. % 5RD^؅yZ:0hC7ݙiD Hv+s2euML*%Pn!d$(yJ_Bʹ>ײA$L#yrވ]RBԋ].F0[pTJtit_ݽ;?ʗ_}n=}&Ctl-{[ǭ`oǟ~ZVz;Ѯbz:^AN}SDu`"_DAAAA䍂A3 QRْxh3;֋(\ezeOF-˅g(QQW{eEfId( ]!`M;{> 3<H: DifWp΃0^Bg}7~? ^Dbu#J\*je eh3!aLFg!G\IcBu),rIQ9TQ1ًD9J|!ՈS 1MFFҏ"j]0F{OCs3j,$24KW΄29:Ɩ3S'Nw/f2T\#X264ҽ"Im!BM2qX9'U/jSȹy7EɅ-y(OƍF;wr_??30hooE3ȕ~A0;/>o~ӝ;4 [:ًuʌeFq<(AAAAA*fA~{R,vM_hf^1^%lHH#<97e졡` 9ӄa"5;}KFJkgEb($#֊.jN@EIR'$mYzh,d2W_sݝ p7>Z%h fŃdS1$Ô8"%~FӔdTWiNB%ĥg-1=\ D(,TU?EZy`iT#ɷO#NWɺ.$Z&vO?er?T,תU8qc^yP V X١Pv=$5gz3AyNXe畨E zDxƵHuSXTJ'uۥ19g̎8S+VR-{(1B= Ԁ__|Q +8@lVKiPX iE>s."Hl|-     ormukAmbf}]Bl qhb=L18I9{N[݈n3fBɃ9uT0;d- hF̚iT*%<*Q7x6HU,'1"|QlǙ_XתN9?1ܜBjveWG簴D)mc7{kԸlBDfll2gBjK}B,KʜPX˨H@Z"E}u'eTjaqR.O&jvOu=z{en.}HtT2ԃչOFyHT~ B&S΍ '8 T^= q`|4* U njѩ+ݲ`(@X"c1Bf lB~!sL:Q^ʒC J.Fg"d+ .c)ϖˀ+1ZvZ=nM| nЩĘPS>~'@ rH c0uh\EZ "0i27?^5 Le-+ -/+kyOZ}\Ǔ OyCx#  \]GpHyW1o_3 o'lz]vld`,D+HyC(ڴli-QDlF|A<:\\b(sزgϛ:;8Jatd$xHLǓ?sɈ-d)rp))RI½ xOA3‚^[ioQ_X=.s,[)reuu&ZZIT1DaLzm1*@G(U-\[ DF\Wc7G[_ZI:rz8^Zv::77pG6jMxA2B>B7xAAARbj w A-^h(G$t);ĚdTp2FI4^FHtƝ 2TI*0\NJ9ת 3:=Jb+$2K87ⳳ:tb2XpjκJr-kѵ*tc+TislL=QAeYvy)39d*K+Du[/nHO-Z!U+E3_`."J+fdD1WdL:#t/<o:/kӘ/X6ښ=Z `D xF2XyZCh?OWdB9{ W39;QR!   r. bM]he~rfP1  Ʒ묥mE-Tc ْB5cu% r=|Eɩ8,*fA Rd;*lŵ<1YڑC$N(%8؍C w? ‘SrFUי7rKUE0S@ľaMHOK$7bQO+c-IJ REv4ݍlo'PQj <@r7i$hHxfϚ:Јf ǔ҅xqLoV1ʐ ;xUa*F&C}tJ3+ CptF]a*HJ9bYqpB_e6    0ʘ3r8T#e"Y b :\P1 0f>EɜV=gɱs*1Q#%랳٩xJYU0  )_$qTPګJj~e85G[AXWŃ\v\]a%1-MqMU2iBQw )B(A,JWy 8)>('1_X#9u#t@u4΁gl 7 pNȇah#q, KV˛n>ӎFO{@\ȑ:rb30&MH{ "KVaT!    xL3XeyGT4흾MՋʅ=*fAateϾ9srH(.vh/\]8JKc\F-;~9 Be6wi!\f:N"cg^_X̮C)=?ūMxϊ-7חt.I/pnl=\YElv/hV Xa'{\XhwRNo64M̩r͝Zk+ |W@ @ ܆JԴu}QԂ3=f10fuԫ·vTS{:"1fR2..a4?_I=l6 >7JFhİp9>˔OjfF? Np͑v)Q'B quk$u"Ҙa'KAJ$̕lӖNXoN,i6Ӎ H'54iT=M:jѨ×4YXY_A,*j3vG]:9Q70ɉiLPuYr QaC{bv$wbp( M"ATͨ\"2K0J @ @ igv>|4fx8ƌ@ |~lB݈6S-Ϝ;I~[xXQ#Z.*2 ^mƤ`8HPvNwy|4w^4c%h1(1@xqUSQ"QBNLL=M#aB 2iB`Bt&u+Y@A T8Odco)HfR(>.[MV@RVUZ%\۫Wɗ"K)vtl'T+ucSk,6{8p 0\%C(_do!:`# 䩾+< CMݨƌ@y q\@ @ nA19@/_zvKe!@ x.~3]QmQ',COYl7+r,Ez+֣(1 ?̊i~VgҼ d14q1VNLHK(l@|[yFKF ICbbu$! B$GҲGL݂иR6Ƨ,Y/ȴq)%vYN$LpbxOt\y'%KTa!'.JUQ(Tխ) q%l'NC5|JJv&hӆc%4meA:tGC Bԝ˙3ppYc*B^_ʕI0/@:OC7 =yU,@ U]f .׀:&@ x/BȬZfL[BTla4cRuY|6W2՘2az}ª5cOb/-ҢЈpwBEP/`P f7ˎs:?kqҥژ#axNNk;v8\(l+̝J!cSė=<ˌ(Y\) ~j̽FWL@DPȕ |5 {*N L\z0Q+,'p!1cܐd|J|V4.]?~M3}|uU%c2Ay%t6~XWͤ*&AUKAr%M5J++KJ: u|MUJ/Ǵ`b$']aG& Q&lQ5K[)(Qd>y h'Iy_BP3E VZý]X\@ @ ^35a"ׄ_IbYʳ0fG9L6h;"U{6b7-[16~M 4Hy'Y&, pk) /}@V*8)F?-(4M!FZ}zo#Q>ؕjjd(, "֘Qe_/GȋgġJ}KrjiG OJ+ّ덭+F #UڳQ|ɺ>Ȓa]hh4LLU5 Wiynf5f<*Y&jb>Dq`APɫ烞Edbc`1\anebL"$Y+usZ GHq#&8<Ԃf jp"P & ^;E ot#@ @py=2>Mp|΍)k0Φ!@ xiL#3%Ecz+%y(U111ƝkV"GKhT>$.05ˠbǤԷV6cFQ^~9?*-e$&>$TL"̘dfQu[A*ÄLyBBC]R] Lk:Oa=Q4}_+nPa>sH@AKUmx=LOhpd;ޯ*lTQ+}AsYK"T]M}ߺN`=آq?aSwA(Ok5Cy5S\~Y2{lG@ 5!@= 7!] $v_>ndaȕ;o?v_2`!uQӫecF :]f>Ԧ|O/ 2pakGTȫ`eADƌ iT=9 5Ce F0$Xb(6ZrAeS\9x/aQ'heI&t@MCd&nj_l#UZhU*_(cW^P ̓`&|><5!U_7[Aݣ>F9RZ0j53|6s`qX ,R(jfjdbY3:qǹF vf邅OAT@ x(Vo~R9@ /qjvр`2c䗂>iN)ͷ>Mp|FNm{EQYi~!@ x;MuvKL4rJʬ?/NE@щ2za 1fsB27,g z.D!؁Umd.b7jGEQ8^D,2B [hEô2b&yX&&.,%'4QᝥFKki2Fsa%8z)M`Ղ1piTb*ZLSv W\PtRhżtk"kr<]1aI!}&dKTu-ųLA5jfrXaN~_*VWuY$KԷ:48o9w$)nH"4II 2'Ȫ(i'( m~JQH~Lp7>4$nn#,N=080Q*uJBD_kUQĽHGE,cZĦ1Z@^?":#4ID]eJb5'8d5~P5firR8Ob05(\F' Er TF$MLx{5f3F]jJ gcfchfBik]W% s2 @ 됸 cFntVqHH6Kd2 ^1g`dvnn~~ʠ\\\j}U8t:#88>??>6 fc(|x%^|ϗ fmooollm8}ȗ_xS@ !u0gu@;$'Rɏ!$$!W>>iKRY2Dď)J_̘ԻaD9~hغ"`˖`6{E)fhjP3- j&iNlI>Bhf(T"@(;ֲxLA!9@Wrv򷎁F/QJDf=g^F?w9eMn hP/W ۥgY@ 8mJܻlϯonnnll,4 g*a욦h4:;=ILk-}oWy<5_7Qttt{9?;z(-bQt31>yhpy @ >n<m,Z}ӧO.~Ct|87ۡDL݀vByWx<~} %aw!(? le^^^~rB cv}rrKE@4t A̓ p80gDhVVVW`^ģ9@ƌ@ ~IOVQD&)+O#1s_6p hX]k_uN$ό/~%k;tu"3&bdH_E㚍WRL C(Rhhd~_ {*ʠ Z*ELg2'h~1uAZR(<}*=v_03& l~UʶU8'nl6I*1eJ"~mreN yG|Xgeה`YWЪ9c0nJ+"",ZwY1x_ mr{amW&@W$iv_啕vEQ93V wF d" (nh40hn=<-tf(:>>%^ @ |H&ܣ$tHX^\\Zcp& B Awǥ%Ȫ ߦ CpƻyV4,yht@7 b7kZ143L8jC_#@ x>E2 ˰YRPg9,1]&TBhx-X\v4cϴ l2Gl&af 7.j֠PFA3]˸/T]ti2QQ$A^ $Ĥ`͓bb8R蹴vLih * 23.w: n|J :TM2K,UqC_Sɰ;+OrTA%' VpJX*fQli4AZK(l; s. [*VB$'@ܺ㬀mh@ \3Nvkkzɷ߮qe`0e6n$I27;;НiQ Z- (dxQ9z/ p>? ӵ SVWW+3(...a-|@ x{H51E٨\R/ KY2bb ];RIMDȬD!dޭȨ*Ps繻%P)~2/!ôTL2S?Md< LBrVQI€J?5y-dy$>J4`2 3# 퓯 vr -|pua5\T htBHk uA̘rŠTҁI ω.Gn&\n~mN{@q*y#]4L@χj5fCƜ[wR)hy!@nC5BEFc~~~}cc{{{mch\GG'''Z 5͹ق-{`OZggg;/_ڢ0ax~zztx8a^=3vM yCgi3-[΢Yrj F^@ ೈA8af3'O0]faa~buᅦσ^W? @ |sy.!I\p$^.$.'PC E+K U$,Lu+P0f~gLsX } omW:i#%H•(jTx9ɏbJ=G$6H4ۙ}kk HAV|ml^*.bh}D53.3EAu]`>PX)JHaݰ$4cf`\] =Ԫ&M|&Ho*Pae\ssHe0|*I5PtqGhۤ8 L4,%"[d]e- Wqhݠp9 ;RB,*^[*3_|?؉fm'8{^15I+M N*\ =FkU+L&?p4r֚0Ld=\Z_XPZ_\\F"ϯf{{V7~2zdzJ)5<x G .MSW1Dʹ;*7vTON}zNOc| tRcoLǿ@ @ۘVѣO~wðOӟsoo@,P|֡}2o^u_ia:zMpQ2u|-VVVj{20Ϧ>*L9wPH@f|G.g}K dl%Jl:ZǦH?ʊ +Vf0.2w&E+D2qՈ捉ti}s81Z_15]Һ4Ӊ"7AtoA=HHnj='{xd)VK5l 8tV4c{BɈ1k[qDݰUɾH;k-^vM VbEҒqRQ*$wZ0Ƒ$ 3cw$ė" |m15\%? 5}TCQDsa@ _v7ɿn)[ZL'y+WjZ #zn/,,,--O;/_{~"=EL$Irppv(e?+[ &πCF!:No^x2y; W1`>T)f:nUyU72@  6t6cuz>e~GHC\PŴS'9zV4#x|zzZ3f'T!eLP'؞"c4$ C~7@qxC~䝧?BaTZUeA^eTlb/&n-*"l&jCt JFe,Er3U r/R:̘W*r TbT bSt+TB= 8*TKwOoBIXzwY_,9je,/&Цk'p a醺UNNOĆAZ !^AſY|Asj# @:o*vj%k 8 kPAۙ/ӢWti5U<4.6}ⴰR,@ x@CF%qh4bel~GB6 Q囕J+fId!NWTg|^1~QUA{Q x[M@YYvÙVf.^MU=3㺑+r㙺rZ*S=u!(?lXx& YI >^T@ CC=Va3z~~x>_]fzαt(Ŀ^;nѼl.ëTj>xq 5f%\%8aVVV8*[|kg ]&A%+/\@Mjfw~8,`zu7fL`|T8Ȍ)jDq(&1$I~Q4(IƓtMy1n&4kq8"ޟ\ eLy-]-543BiR:&fF(pI$!a4j`i"O8N"*qVe9t> hkJdZ|{a0@ 1&cJu30-)TVaYg} uvxc^lA@ =xtj_?yѣGsss0v Lٳ^elkxokv>TxHc#.Ey.Mk|ԁPP3fا LGZh4iCu'L\[[(ȾKf̰ eqQQF x7=)Ӽ!ڽ-'NP?}2LI@FqVLfLy˛ZFfḼ)|c$R qM&QzГhQVE|E 7EOsrwJ'X%3& jEK$L24C&Sni)181+33߫r~f%I~ ~a32@ cj4Ժhtn;LQe`T O>y͘^wxxO?}2l>6__+D+VR!h:/ > 9+KB14) Z +B6U FmQ:AI3L@Ep74óͯ1$/XH+

D. *;,Ӧ%FJu T8 t*Pa3 %g TֲTܪh ~z12n$ eKI3dhfT8?&@ 7'IFV%Iťt2h4)aӚZ~=qvvvcssaa,MqS՚%KLb4k0p8d Y5m(vwFv_8W%v`,Ӓ[; a7Ǵ=J8gRʁ@JkL݆@nY./!,p0(޶%aٜ[\Z\co4Xfh u8>99= pk#:лxkol9Nì ј@ y') ƌahF%ΥQh}Jϟ?gu rKEL^ØaL^vk 0;;2 A01) gH"&ؐ=ޖ7S:}L٩5! ggg&| n pc5É يB-^'p|񤙯1Cf*lvqTH+|vc~BDZ[N߶vFT12G/>FtVt(|+I*LಟhRG l$Zٱ(0Yq[Vp+ Z ^| ǎQuA M(@ZF+[FяL"j9(?TB L4H5{؉)Y) s X kWѨ$ MSV#3As6Tg4.t΍p 3W>OY kfdkulU[Kj/.nj) ,Win2NˑkSz8H  h\DNY\Xh$I܄,J... 0MӬ ߶xuyyynnPFzW1g+hiK#0M48Uow?#>o}gmm #YrVWW766z[AJTDlF @PS$20t8d1mM.a saX?A__*5" pщؗ VCi;e2A<qq`w!!=o+$(|f(%IiA37!2,AXS{ڂX5!a[~ł*~d]D_#c|9q+ݫ@ UEuAèl%Zu.d}+N$%W?"-aR}d.8͋i~cM UByy3 fF%Y3;=MlBnu:3Kg̜ kKbz3(CJGLo`pxxxrrr!_$f3O>88} O@ٓ'ҋKK+++^VB^y3Jk+P'p onmAе>a@qn`/[4f`quGoooj<2#b_aрp05kkkKKK^pooyhLe @ <Уd 3/0/,*yi A̳J3Ph@}qq 9ంBx'D/XnC&X>&^69"d>;; xOOOwww;&!a NWG=gf qUEꫦ+n_s/ɓL <(\]o3oܔA$ZL."]2ebtEiTfL."|䐲`Hf1B't ex$H)6˘6tW[i&@ԙh2bZCT@1c"t< j&+P ߇%2N Ē; Y t>t-BrJkTnbLHT$:nԶjX?$+% %c3g]Yu}n2cndB3ߨ?#$l+eYz:uyx^ ^K@ -ylCPpPH@r:&A0٨~㋿¯; -%>ݨo7p;"U=@K.]C&kq#]0Y1Dz֦6h[ִ܄.2]ƨ3w³ܾ΀lV^OjeF HA9QDG'P@%:HFL\xo _r2W8ŤpZ\VJrgڱ[L@k¨P*"[.S UrEvjCiNrvhKP9-Q327jEѥ=gv#ڊnWߠpY~Y8g^|V^BXu=wßX'@ !˲^e$4[^^Y\XXvQ73T07"鋗/~~KMadYj Nt0gRҎc' R< j[hZPd69jZp8D_WWYo^|Sd&P!EQt-|B^G˧(w®O_|x+yQË^hqii~nnfvvf>fnmZq.ϲhdI:eãCI( Ji4:Ŏ0D9@U }`I0-u$c k*<;7ۢ<h/K f 6zqa\ ~pwg8J1 @ , t>%8 uaPaX @4u@|Q.` `JP~U =KK>V2@pA*yEjf OH'Sn68bqACҡf.`MYбՂ؄_(shum =B|7OJЄ[fLHIX[I&m)0htۡEC1[CoaΗM4@!Zm]nڧ}>`>1D@+ %"FKm6 rL\ RY5Ẻ sj:CkS1t;E7Qd^}#jZ:*qD u~U!͂ꆺmJBS-cDҤHCr2ŒljcCzU`[Z,jPhD4Z3Qۥ/_WY%JAB,=+ K^KTajdM"o8JӼ/S@  5ӅI34F''',W!WW7WWfh6+l4z=j19y^+2P2!Kq`lk!L8LxrwC`rn)d{28ifh47aaE1M5V\l4!QVbPDbrX95 4v;⢰ 4eˤ㋋ HCP`ni@0_YYa+b ^2:Of:)jY6@Lln84VK2`!HbtnS|wO<ڂ~"A?t+DC˜_ 4%FRK+ ̘.S?.7 6yD&@ރMiqVB":քs\ ?sEÚV2¹ fE`t7DB 6TbXNZ<݉dHA̘rzP-O,Wq MO({m3 ޶RׄD_ 1 M Fꌆ(lV#pBuEYqU=dh_,3"˪|0N*3{K;&'M m.Ft_+v5kLjxF.93f |ghe9zNCzFrP:;;[\\\]]]\X]\@|` t@ 7<>==KKK0^YYB20f8` 90 A׫>]@o`s&L8k~]@P@ Tp\$l!a\>:2#,1tSӧO͙(3"?ӟgϞ1]㾯'ƌ@ Z@2j,0ҥW1]Vq#ߖi~ԹQKa8zng3Dy܈3IOb w۱mc"8i0 y^t:Ϝ9TB%N[ u>ETtƷ[]XDihh͆V-cR숈OˎKyw nl!ۀiՎd+Ol-qaݔ,tSԓL#Q{g119/g@ 4..XИnprr2uf2J/i?M:,Pڍ` y+_O5p;Po':SVJ{<Qؠ  A&AV\W}@ aZ++b< ɤ9@c8&FhLvV+2W5MiSS[UC@)_'*zZ:%}aKTʣ[䈠ST.JY?@ {ziʤi///q L5aM Go[R+L+ah(3'ՆS(˗/wvv+ep8 %UꚬTL>kbRE`̯ڌ ~~ٳgJk361#pt$܎Y)$Zq֝dv7ˏb7ʸD"CDK7NgE}5ejT>*2Gyq=˪`ΘEF!V]& L'4-94ZBS7HGT)|IJ^K^JV [&T~r&j󁷚*'ab6IPECIA) dڡ2\Asw"I?i4VF2UJ ,<d "lD=(AiE.٘ı@f0ݽZB2$?{6'5FA1(]IF @0=m:=K  #XiaqJ Ehj}koR*zǒj^keKSdAUWȈ_gk"s$50lb;nӁ,a,. xt:fׯ iF @#2&A tWVV` `d|T܄/\~Nᜟ|cCD{ $y7IΪvL;&|(^0tٌ2P^O?2lħ_3a/RLͣTL949O N=fDy@q\rS}mY*R&MBV%B*Os rJd`$IVWWWVV~;2a^,-Iid@ rCsLh4˼וH6r&3&4YF nH(j[EKT7'ul">GТIPKfXx7P }!7ʵatlSM'T!|Thd 1iz-Q ;F/}#ٍ-UCs+֕!g+gTf#]Y ҕtĺb&u}Aߵ=]5"0#@qGwuh6Y;L2"L kό I)#<UO'1yqEI݆W|<@FyS"ӽlKfL @'R3Y$' *|,>OsWvLƬzui"Od14-FnX,LP%C) 4~dIf/ oc^KY! vnx5uT^?}}E0( I@aNBɄnÚm[ ?{|&u9$0 GqغKbr:ݑwqXF&Aa|7h)*.EEe'v?-{l E~YTDaPj`GL}(deagMpO_vyMUynU=kd&bW՜ݢ"kmM7i&药mݿ̂w933j52[I juuib e+lء"QZhT0鶉Dq8NADr~r`eXᢰ;iC:ZX:j#ƇKKZREUdL%kA[%tPJT ˴Vva&їCF hs +P3$!Å QW'C . :Ycp6,,Ye$?̊=YCl$=gQV@ T3㙙oUi}_\{h10pk[ (|5~ggf$U^ a,MQ[lno?Z\\rp |z>+Aҍyt^Vb!t>WsTl(0Rh:4PE"`91u;v=7?>1( Or=zv&܎&< `GkkkNyZnCyڐv=>A _J @ ?jo&~ ڳ3^X? 8]kॺ|:ZM0o40_YY ~J䒔>+pD簜5` ^UWLKKKf / A k^V D%O\:PA?sg(ЖL{GGG??ڌ)ZcF\fԹ\{Ew5#!EUzggm4;7|c|+?߿ZRnz\:Ų$@Δ(L)МyyUZǍh%j_hQ|'rlŐR-4p*4=nL_}~1}'ga'O ̇f)B2;Hi~9|}q5Ur,UZyU&"16+<ѾʓkU<,ɬ+@Ѐtl'zI N̺e})VQ4Opn'wL.#@qj&CuI;vg`0@{0.,.-,/.,QNOOF'+hLȆLski50 O/onl?z433SlwqX|&n{|23>攓$][[#b8(6T6XP^/2~C߇kf+? ( |@V{s=Z ?~pзi8-f`5ȿ1VgEV5#/> @ 8jNc.Pð>H7w LoH3+hwwzѣG1Tq*VBT aؖ=}U<kM~BvVWW!fUF13o; J"^^|?<{ !~U_%#'\]?WW0!t:::zᚄ.`}cӧQ]u fsk{_z}}VS6O!iu0 anra~ tC)O'OB>X~;3În CW&#A/^YV>>;`?~q]`B܊oS><8 };3ҠD Z՜AO (HƑhA$Z'eC/ɝZnM8{͇&5~^\]ߕCҫOk BDjcXהHۦdxZLz5YPZ!P'E*RPSt!dkR.*CJ"1x ?qft7| Cbl~/Ya_9@ O6e5Cg[>7L"s&4v{fvB~wg爘.8`jhήQ47ba9/,.kqv~tc~Wp!c:_4ʽZ䆆9 9EðK-qNE5g]Xrvzw@l!PZC&P'''Vl,,,4͕U9!lMC΃ggr4 76d읝IVE39"Bp*%7@ %* *4MYؐ×/_Lwoy-NE`ϼ 4d !`j`H15VnB ]$=Ct%|(e`9Dm@@ yBB0=jB?Y]H#k|ao?VI ʲgۅ%oj?oZ$~7DPE~8Aӛ9///O㏐mzQipÒ/ϡ@77;[/_¶Ћ}_q-,.84_2|B짟w{=ι r~ɝGs=kkwPO~ߋENwG󛎞(\Qaј_XWFj6[4;>z+TLdy|y.]"ʰ#!)(FFPl"c=?am>;IUS+ S0LKK3Zdh$-|AfLP/?&h-Fc<-D*Ұdwa_ Mf@F{@ @`Ȱs3xy3P>@ ! =G=IU `|"XZE%':v3}lO-$ oVVVNlBX{ 9'͟Fhp"$`_P0c 6$$t˜yh\_xsX^]}__e?t3I,Hӝ5m2??{s@:Mӽ pjcsra5ԧ + J>y[[-wY8G $}K0fsaiik{;_d}%¯pEq?+{5^@?Д*hMȬD&DbS%_czjbѐ+3`]%CNKJSH31e'dkifL[@eF %TۨF]BtpdZ T%3ɢ_RѕbO"%7r}CS |/ȏ)@N uKܵ_\P֓p3fBͺ0}&N{( mtȧ<6ȧWX蝎"uX#@ΓXFckq|uumaa3IFEhEbJq9/^|O^Y6ϲjCM2F#N8N ./%;;L'iz%I+"9&_',4FGWfjs ~`x [cΪ28|(1Cb "88%\Shw8͓ dRڅS`pt| yr|vLP?X<|sHiJ wf❰ l_!s]+BNC,//47YCH0>axC@DV*Rla}X!¯' jς O@7^2nZ[>|#1C3 kbۿЮ13W_}??iD2APJV&7G ?\uoM,Ln}? = \ӆJIlmo7Y][N;?Ս4ϲÃ)2oo_ ֆ?sLFCz=ɑNߕV: ԜVY-mT+ ^*L=V*6%3ř@_W(3gKuM"k]=O,Ę3 Vq-Sea(Dt/UX*|n.K+3F\-"~ hkiH4W>w`b]19 kdкHcijA X/3/;3~ܹyBp}e4A1*[ь>Z-z͆&\tF @I}.&qyѻ899YZZ; zFk*,쬜{,Y8FcUav"ֈWEչT&LK\E>엧_e1sѺG"y2F1ӝ/ ,My; vi0TaPfffgg;ND,A l @}BINOce͡zCiy|@X' $!sB&(0s]S@_xXGPx9@ k`B3HB8C$ Ydq>0P~J3]j# -Bۅ=UK`Oggg U*IP`.)Fw5&;$[y9f~~Y;,~ï(p;2Lr>`C<`XA a Y v׿Ǐ'~! ^3z(| h9o=88}#w?=[\Z\^^޽α*ACHMdDTatl|TQ6FweUr>;0RS;  WT ki IhN=Sח|ݳ 9M6q("4c}ӋIvPaRJkj*$5ё (J5`90JG+QPG7p -τvƾ$X2̘j | ZJn5Єt]yXWٙ ]&] z8\ M\Ȣ^]Z+ה@ /z>:3M[ڝvL#hz9Nbrʤj0($Շdѻd3&_xʇ_*5$a`}Y4Mq$`~D/ /dߟi#I RA5G#)XQ1J ~өMluY7Mp*ӤgMLYq!_' GZ G' J'|1j v]8Nh!Cw+@ | le6 h _BY`=x2f h=3aB'6|OGңYJ@PkX$-x_l @;``YYg^&sw:1fX~ h>jnw0kq˜|H0[*WWBGGGЯ߾|uyz)G{ksF 79=9¾׏od# o;77ɯ7H0:AHI C<(X*BY6:R%C]%F&A Tc3r g"Rj wfC#]&((ae2q~gEtɇ SU^lQb ˚Sy)~:<[kiz~~~yy_-ax[ȊuexjVy؂2K[%:NtL:||/P9"!62$Ί LB]\pQ Ꙟ4iB!U4@ > cl0 : ^B,\:T5f5FZ; n_3]M͞'?8 *^ tXn n0]"Sr9܀0f vv776T[Qո}$TN׿yŋ#_7'Н--/#A}=Lvwvz \^^n4ύN_Op4rB}49+xR"% %(!#)2-#BQ: Y!=WyVd`h d/,Ց97t~b]A 3v%0to݌1q1tʰSv&K˸aնeiB8Ȋ8/G]W.*JJ0QX?DB*Lט0!:.yhznFΤ"> Nȏ=d]y4FVɀ_+u\SvTtGIMNy gr y܎>~<oo&܎x+.j5=jC];".)1#xnvmn?z?_Gq`0ijVWWկRvV*p8^4@?lrP y~i8;$isD3u:9<˲S(8⻨;=wX˅L=QTe4S:=CDLGh1m) H0k=G~L1) n?+r4j-bbTьeZ\v ( ̘&fI~ٽ2|iQ4tI3i}Q,VL^ >CtJd.A) vh8KC6%1ьt]&|Ԉ֢m,w/&AVL㽁; |)21,"/vJ@ MLLs~&Vu[TY[6|̭ܶ11(߶* tzvnS˹@:7 _0OlXWcӫ_SOYSo @ rZ=W:G7<˴fSM.moz̶nׇ Lq^U/apGQ:33wwhc~{r3Zomno&e??jJEßoϞ?ŋ?aye}Ѡ+x' N&1zfǷ=? Aሔj*GqHN*х;#%uйcTSMeI~Գ52 HoJn&|<(͘.+NZQAWr2O2P@]enviw7 hDa6!\5A#U)@P"Jezfn$xu0i ߆ϯ H7pD:3$?ەTOMżk:s}]&@ ፘ66zc3&¼1]::3Y˜|e"KӤјX_?F%)|SHdjEfk{ox)?x|c>@+ &=rҴђNxIM:! D鵄 HԠXEյ:qn<(Mx9`&KC^$~,M2[ j B(6Mn&^a/D!҉)woKt[eg\h+ F\pDrWTFĨDYD*X?lPr Y2aRD]$ñsR<3*LvQhSŌ}ehuun:@TdO*HeMJ^2%-܇.=bd6p E in_X/>h@ 0+ZRCex<F iXr`vo_9_&yM95ny %#@ ;_ʼny 'y~AcFZۢ~Ǣ(BcfǿՓOo]`pܜs?)L13< z?g?s7|3;;Ի~=$T$Z;A!'i_uk~* ;UiQF*蘠,\5 P L;[y܎ r 2:7 %3>&Z M EkSz =0>LEug~7c4v`]S(L\z@uǹϊ㼸̘2c+i7]&.GCpK '.vzIRPE-jæQӟk S6ъ >ka d`eX~i +);h"7{NEsQPYvv&L&FkH =14/rvl\@ _1㜛L&4M'h4*_4X@ _w j-ny@3:s^<<c_3Kм17(ϟ?x\"Yg5#}GGOkOujl n8(ECrhuqX%cH-&4fkàҫNQ$3v`AJ<_F#WRmzL S-2q(#Vآ H3]f+h6bM1{P@ sAmTE׃0d8iJcI@ @ CA4#jC+ _˜|@?aEq38jl]/o᩟+sO y|t???cBiV>3zI.:>oٳg{|o 靫BT_5ВbH$h Zk蠡u*  MS:Co ; sd>H2?(OTVގ$ISxVnBw&~-5M؟y'm8rg2γ@ej)4q4XÁÄR'z&ZHJ;&|-dJRAYc:'UPaN s$CU֍'uK0ԝ2@tIQuI~ý^:O.$ڎ( ^c`1,HF @ |I` Dңш)[~GJ @ x`W&]%)0f8:\ƘVӿv;/_ikFQ Cokbȋ"KS·XAL"\dh! #ꊅZ60SE===}p8%IB ϞߐL(24[T_lL&bnQe_c^RjNW~ cg4fߏcM2d@|skkkssP~-T`007O_RR =)EFgCQ1P1%ޠJR2հ(˴/2#G99g "GvKL=2hhݝYك+s@Khh1 mC8HF(\¡Ћ;+a'-[l&̆z6|Jv94c9l+5X*um X?&:iUNJ2u[V}s _ |B_IVA6sibߛ.0`!2L6pC duV=J:@ /yx|Fum$@ P>hGs ؖd%X.% ;do'O0 ><___{^ǰV_jaqbȋ?O?x 4Tڪ(.)_o'yxȊ6|A쌹ֺzeHG#Kef |]ZZo~÷,~\$MhD;ʄ}wVtNON_qyyFtڣȠ9h҉jk HJD$ oWPUuDvazvLmz?~|2ĞA^JoGg4*פ2 +RRzz-WeteqPc7\!1 htK}Zs"к‰]NU͈4cpT |Fb64+q8qթN;H$ =eyCnITe"@ %T @ |~4f|B|Oiu_+\!܀hx_fY6I_?&q̶aӃ~O?^^^pw3˗#fyQ;;ߴR/^AMaww +s/^_a?ֹ wzqE(1FUX sHTaEPptpPi|CN& KUbb"ZjT@9jJ%RG# dΏ_ Nҹ^W30 KnOӮٌ s#֯wيÍ$(C\s(5cZ72d4v(T+8xҏ5k,K'"Kw _2 $4{Ytw/MW=J5"R^+[{gV @ @ 0 C/! qY4WFX3/:ï=bcɄ &4i48vfixyRaFQjuNqkm繵&[1KSY!weEQhc(2d}cO4`,J #d2Q X7pT (*״)87 nmӴ8v22wvn*;橐]j`J]0LH#!̘ih!CYT̓RS:1(!c&[̘ Fpdc䫛7ۅP=ގP't(*aIؖB PДP i=2FEho1{V*ʰ dB1 _JԢ9X8UKIUYB!]&4ۍx3 f?XI2͡1fU3EWYE Wڙ@ @ =*ELeML=Z DcFӌ2arĝX;N,4}P+|׬lt+jHcz}+SDzd4L^UX6Lm uCD1 wMI4RAۨұZ5*C (74eNB6LQQd| hO#idx2rhJO|g\ .ꤪ2/R[(*ADTX3gBGk>Sj:Eܠ :}T̛4>z}9Ɏ2;!=&9r1[K˃mK!VtPo7iL Y1"e`՗"J^WiCzh$t@ @ @ x`ҙJ+|sš|1e~*u|=(L {~XǪbZe+>^\r^oU ~*Ht ~FetDe:F+{H̒ QrXMր=Kڠ9UYiwNUOdkZ J3&21}º̞@Pj`Π픇qG11NTFڗ/֞[/P3Rm!4Qج(3P9b?+z>VnN hSytzi@pYݬI^a=z^Z>JU"]yI$@ @  |zhE(]&GK/ħO-qd,1Ō0f"|y+gɇ*kVՃDFHL1n&JAuT91%ĘU^T79)nj*L{_8ݒEݗo$]w* nPrTZHe*9ur}woQQ(nPfqlZnbQE(k eyc58L1AĆOz=JDUoinbP8{@ @ @nz. =2S1f/xp/ cF -%Oܗґ!dHEH41OFQV$!S̘&Xc2Q^RM߄'TZV#P'tP!R55ftUf}嬄rAxrQ 4W-L:Z/j΅L%q6ٽo@w#LoaQ]ƺI2߫we3Ւ.&dAN?"]>*l 9O @ @ >CT>4Z| K;RY§ƌ@|_|bR1JhCԕѺkP% ʠגh֣6 'Y KP6sкac\ ݆"$Re \lP`Bu)8p͘zR^З P"mmQ(5Eah~r[)4:**͘j[բx,kM=ΔT*lW3ኽȭ N$ϊ!<)UׇCq8:c뎲y_F0>.Df)6] u ] /'li@4cJW=O@\cE*=@ @ @ |7>(0fuxISF 6K iDd4uc`zY)wHޓ >}MRڀlŸFҩeuH2%T9{{ vh(缟 +}wT ebDQ T!3Z p JBwhBSڣy6̅z+A. Ǡ :ZNjb."!d("|Ɂ2$#.hoXO{X_@v'ǹr+WTM *]?R'/.3$3*ue3Ƈ3Ft AI&N_Z.#|w!eV.Y|d~m۷Zí$ 5Kch~ZIz@ @ @ a"T0\6F'h:Y2LIH:T6CF;3/H ܂#( PcJ3ĪyU>M LWBPxtj*v2p`.2R2cB_)jmE@ @ @ >1#˨0a"ASDH cG 5-!~L4m.H*QRFGCs(HTd",]KuA!DVJQ9. ABك,!,54``*%E"> ` с{vW@zDuٮF*2;+fnKzAAfވF;Z[hdgY17e|e+KRD!W@ xDkq0eqVt2DY'8RĺܾH󓢀T̘@ @ @ >1#roBP$$8FYcF ktjh4 ]((]> ~ Z/9@ȌK:hCWӸ,5@t*"IA3eTL2 bDyTT&֕QmAXTAuVQ1u`;KTa/^B]V2fL.3kJȠJ3]e^I_Gh92"dŞXT S\e+q^Y>ppxq{t1Z=JuveiN,hWRұI* @ @ vcFYl C+<5)h f :(DэDArF"%f#R#KFr2\u#ȃ !3$kEDY><{:"C40?F L@h4JhC@ddhÕV9.1I'x%Qк iȺ4(C5OȖQVCҫގ¥$*E, ]~" qhSsuE Nk3[t.VRhj5*=mp3Ʉ~Vc$n7Q CD)]uվSM{r7QQh0sJ\pT_kFǹw0/;bx/}F OPaB.֪W(TT! 4ƌ!)ʟځs|#CTi,cQf^Pj92۱Y x91gkj>ۉ[,(\p/&^Z m \h֓h9 ċ݁¤ag8~PZ86TVf5~]eWH&n{=,jNPܼ:T%ĭ6\."sgB ]h6F:C * 2JEJ ㍟?2@ kh8k˜"OF+BrIaQV0E^D:Y }՚=FiDnqhB^F@ X F0A.`N&0oC%\d0_ƌ@ @ pL3fjA,0ƌ_ '4D'LdVg3K~NFŪ.|Rr+x!,P^Y;pANY+ Ȕx$J;}AEL}wݙ?|QR&+lL 2P6FJPrY i2\U**x #/АX2`IfYjᚾ˄f+C H ֹu/IVa|oRيy"NM-ΰpn3&7$#U76|9 k$x=% Cː˫.smJTَù;]Z!]eZӪWIÞО.ot׳b⽉beA}7|@i *΅"'>p h%|sQQ]6Jnnmq;nNNsz~@sT?{'{P9tέhY=3ٽ{}?}w:ʹ{RwrP@u$~C"Yrmj2zPl+w3o aWA L-oEuS+c m1d/k!i!\@&2|#pGTãAWP(fAAA?ȕr1sگB\{wT }$ђ,a .UJOkDIS 4G"Gt=F% Rq#Gn쀋aR[333SӌBӧOwa\^___\\t\:] t7%βR&J~>=-R2v;c%v2d?/>J:y1,kLLd2I߈u29[p2\:lơF.C| %1ITzNkd/YjY\*#r:M9ts];bM9aMKm@>̲^|& dr2Th'5-9O>{3;taS,zz^ Nsp33|oR|CDN#y( /\h0Ehu#6ъ-IeAAA/EI K1Aޘ i~FPbY?F-g,q#Tc(I3֊S"1 (*P $q|.kaJbtڥT/Vfgg;Nf13;{]xjjs]{O?ݿ?yW^xO|7 ̨5ӋKKGzB?Bez͘~O 1ʈC,&(eLR(ctf%3T %c͇%I2YV ?,M#CӜc؏ole3y4v \fGeʵK3VRTj'^ H +L2gqHf-] ϛR ȇ=jv#Zˈ3Adjf&qwi7pp^x񓏯YB<#1f\IYU@1:-tYZ >,9ひB{nXFH#ɱ|a\Ɗel"&Ti'*,laF;I=.C~ +*mW[eS)*H*AQRUƜMW]6kO4R(Ðu%|䰢D/a)ǝPك8UduDʎJr37@UUZAc^<>iIO*&HTuX̱(]vټ,N& bw]GsS}bҷEZ靥eEW;̣/?9uce5Z   r~V7(obv2ԄX#* yf.!V*?[ RTcH( V< 1*\L$PRB޵"!GHbqeiNf?KW?jw'URڔR)uGGϞ>}) VkfvvnnvRݽpX,ۭL\|aᣃ%7ۭw' ~ʡ.T;t[.h[_ Gn'n]e0zYi|*7+ƻ? ȦD#gr=\M9*u4k6 ,VXɘ\ ZΫ+I&KS)빴:\ю"3"|`п̖}tѶ]Ƽ ;Z-)s,;(qUa-RIg|}B?͕=~ $aND)AAA9Z63AoITK咢L"!(5L* |SӔuT21]ez+J~ER+Rz\bs1 y:Id&Ͼڵkέ["$25.0U,^z}vipJg)9u.xJ.c5Ǟq1:F.s(wyJjX)rKw%gdS2m9c2PF;D&=޻D?2L {Z.#3)TʧDLypTtaISV^hque&6?~f/\f0eKSGG[;~_hy0"vYqجX&!B3"uJEfBM!/z\ɘY2B<(]%ρm5i2!(A?(r>Ie\عq?iOe$*i.0mu2&dL]AA߇yAAA w *f_^rj2%j/b^TYX91.%Fӄ(iԻzbS%!FX’PzQxJ^"j뒰 9DpEɌ=+_UC% 6m +4SDY'3iJ8J梲&1Q3viJrz-Mi,k5.7J=+NI%T2QP.d z\! \ $V&!!o#kN$L:HM/G7;hR:f,0`󈿶Qo$D &r(eni&6#auNstɋO#K'WS:i偔d 1Q#Rv>F1#qyIX? Ŏ0҇ϫJC5OEdǾph?QHF③xq톥 LSmm-.ciK)}7>|Z; 5K2:7r&e؜$F cVKWx>e?<`La Vq0L%džgG:Mmu:Eqg3 4AAAAA*f~NMfx/0%j-)́SV%91%E9ǨAJJB;XT˾^V8Ff\]:rX/ qHYFxl6Cpnzf/] ;]\\GAل>rttLG3+1Md2\Y\Zr]nmW?s[t~C+v{A$c:]Y-Ym22\F4p8 k׮A0bvMFv]Vȅ`rͥeq8I8ǃê|Ç58)PgPp+WU%ӧO={zYDbvJ.B>&=zhkc$!v]])p8L\ r槟>vPY][` hN3 sz7U#   *fތcL&ӹBRJ;TRET$'ABiKX2jP* ֑(]}e"Cuh,uՓ<ȹ2N|gB[!E<%QiCY:ޗO z踃!Gh/LV)szjH/w-iNx6|" # [q2c]-o./Ie^tXs Pm&b?u*<[.rFe|[ߺ vA )sk\\Wy7n~w[ot:^zʕ+lmnY,=[ҙLѨjQfsss33`5]|}_2la- B zE''`\b7noVVV(Sx-k~iIwD2c]yMG$1ND$:_\݆xb-7a6k"/^/ FI8mloS=)MMO_~5( ŋss?C6 Pqť%Fٱb&Z믿z 3rcM6F 9F~gНV5Ay5VvT1veϾ{0 ȇc^ {*f uOZD(9:ߐJ9$eB<-.ReԎmf$3mwNV1J;J%D b|- 1Fh=34z9kbi'%2K,)JrZ%c|eki7JMU) :5V7:D(b t1:4š'Jh |\ⰲB.3TN6"[.#Un5^fQ.CQϢͦqƅAv8xօE뮭/裏zw{~6Y^^RbzoT:9#dy6'3oہKo8<jŎ0tMSI2Oփc 8 yg- ;3'b4L$CJŨCZJJz']rJlj4.<ί>|C}ĂPIDZD­8uy9H#Hv BkQxSiS|4O3-2V@#!L_.CdGNiVp#S`'J\2\f1E;>oe~ E.;lay/Ը>P}ή0iشMKr?mѾxPUD,rW\Yxj{A33;嚍F߇Dg'1ܓzz[WcÅ`]3L(< }(DTVG-c'8&fûw_םSW_}{{{ZQ%mIl^'?ä_&Բ>L6 ]@;adnlRGs{?yL;RK+w(F$9#e HJ$8=!@@ā[?1)ev|IR߿!^h8d.--}g >yo`\XX gNg4S _,tO+`]b"g\ľ&3Dc@[5gdےdԝͤ;qA988Hh̰@bP0h6gx7JPG-SI"v9O TAA- 3ìYM''ly3/~hɌ?Z+GJt"-QSPR5TB 2:^*JVu@/ȗvܫzK9bT/2T\?~i%)}Izdڬr)e0/w?&M=Oא.C钫R(wn'c*+6~]+̊;ǔShF򾐯zԜˈZ(GaG8ut:1Ύ]M&b*hPN8&;#1oM4uƧ&'N_3ɫy\.ϧiuu}Wbd1ϲ^,z^G[jҋ/..,j6ǺĴXG` B'N;/P=jWK};6ו&WrV#f20z;leƒJȏυ7 ȱk&8`Gćfyn^ P: nĊDDIϟ=pX,WWWgffVVW7?OyO Y 3 $6)=9#OeLp>Ysv_X3vzvm:eA_pa)T*U pZp #-d ~Jxe[-mE2 pV vр W(Pu:zFyFŽm0BІ ;ښ)rĞylX'gUA~3i|!edYN8z.DRq zZ ʦ$LcҔ:DwIS+o%0ỉ1bQOK<ϥH@;;Kz[vX*]to~z:a]t)03ŋa}i|xpxz=:brZwp46Al[L)+++kk33Qt;ѷawT*v4Z._rիP7G676áyv{әsزJP[EJ.'UC 0.8jk!P jFcAnċCיLKGQрf0AoÒl>?;?yM DH8?* *G_1n*cWWSSfT;;;;;jL͋KK!vA Zvw50@]p4rtDtgȣBz>:B0nmm53ُ?Z5;7U&&91Rimuueu>Xl6 ]ӹ9 #S77//B&i}}MqB3|\l6Ãhy,=uyTA3Jp!0??DW+ T_ jp_ l KKKa߇v( #0 Cq}Ѥ24~L3LK_  0Z}rdQ5=Ƭ#'jH5ķy?&y^O^|$CwI1"ƒIҌX&(ƒyZ_Bu>#2F$ο ,U#*W z1r($2@l_lZNڋBRozckgst(")/e?)A%1 !//@_QI}e&%=$M:143A]F%cZqcI ̛$c/ae^t>IbǏj*|b,Ei.:L5L;(*A77𯈼u z˗_5N)i۶IT*߸ŗ_.-/3Q2.7?k׊0vݿûAdsk|eXږWVR;w676e}'~⺮^/^>Z*Ţ3ZFԩBHyv0F~O>  7ݹ}{? â84> (3fsvnP(Vsss|*T'Jvi{z >MϛuGcy+'#3a RZv"Z.3`8eSzvCA^cp+-1%T-H!A`b׫`` ^AUJRVcLfFl;sFaΦO:WׯCԂ Q/\xpw!(Ako|R.c<777nxW_A } ~u o[&|(Mػj?k$ao\ZYdF Ǝ~łh'4b[ܻWT;!?ADArmL9j~}ptx 7oB_aPPGdջw>|L ׯ_/k(l )P]pA<0A0 Q·!DÈ`%4̼[_!1 %6w:XYV 3Dx5M`M#O"yxڸyD1/--ïpclI2  {7jG,S+NJpBL3C#F:}MVA bFNZZaV(9Z#iF mtWXjJ,JG-}RK^ڱ˅IYebBKbuW6Ɋ|K(ֵPmoc*7ucJct0iJg & Kr?/,T667 ePE&) KKkkmo<tSSW\/aلW-jhgggo' f<ϻp—_}5;;{xxw]|/ݻ{ɓ'P,:sGӧO{ؼZW6vQ+T*33Zkn~?-.oon37?a}W5tRX%(}~7=;::v^njU+0 'QE8c׬\|c=Al9IƴӉ\F'"A.Ř yӭ[a*NEɼK!bK-QB˙\.c-/FAK++tz8իWKXx9X(977 "*Dw@ \.kjVs޽vN}:o믡O?9::P Z$u]0pnmn>|ѵ- *]e\ð ӧiSDKNR04ێDQhD̕W!Xe 7t6u߸î ԦKK+_ |[[[P 1NC \<>LAT (@y(9 ,0fqq6\K ѣtCޅaEPՄ1ecNcF]`/^3<@y2؋qy&z xe2Z1ӯʤ_O wuG,‰I\'#,&sBRq&<"=$]S̜NOd|^g9JӌJPBdT"FK }9ؙ&b HDK}!t%+PHa&aM(_BL]'e)2l"9du~U7J.K'%u$phM}.Al;;v(}ss;oܼɧnmlܾuΝ;Zmfv,-/oomznw4EtVr9?… O?Gm=:skGQ.s>G{:~t=o{{޽{n16 .^`[O|J1nPB~_aϟY#tyksߔS>>v { t:wy!\l6 _ݻC=Rfry*j- }?P7pIdLZ.hlU ]A^Ąt:}^rl<.GT ^(2wWa+VW.alwbL ʗ+RA-y椆Cfgg!~Bx1N3sss`X  VB۲ܕ+W}zҟ>y B_o4e`e)4c…o܀F-Div+a% ]{v|:OBٳgNg >{tm1h_+W~g{Mn&`!PNסU*nI QGAV*&j 0VWWMN(%a/QQFQ߇b& }y~Fsҥ}Ѐd2*Aw\zz >}G5j /BᥕG ~H i3K ּmlf)-Qi_RK(jN *Yw+QE|We. :S vE'02)$E;ph;k"._/_nT B&'E~8ʈלa;N6-S]pf!Nu'XnzzJҭa=( rh} DQ@l@a؝zJlbzJ+WNkT(Z\Ze>|3Rʷ`}}qyy~~~gg}v:[[[O?>:8#kS|"ss/^Y_0LN ɫcboort+ r zY+ =A]LMMz~xxXVy8 +PJFfW#\# ԤC @P3Tr & ^L?VJ:sf뫶:pAA^{Ih[Vʲr:SFM*0'fA55zJH%I*AC5fr{{caі1.Q2˶N6(f҄_Jb"'JSbT&BY(r[!$HFj󘾒@)c/!.[*P[ڀE7I6,å9V,V8$6 6FzTeӉ|HI Q0Zf[C]PF8B$""Ijfㆹw@e\r2Q|3ɘyjF!vCzh]aU_X "\.7?Yu[QJU"BgIy\>dTp2-b4UÎvg1v EIMtTݝf//040n[޽wޣy%KuZ@=aBV9== MR)K߂գ#(tx/ ?/7FN-X.a&{`*3!4SrUOer2 AwRAZ?noo/Ȅ 3l6aǒڈV_֞{^Zynlllnl@ t qѸA- R RA.(`|4#_<JuANSP %ёLX:ߓѵn*1Fa'd.ɯ:)z^+9uS7L֠VA9ގ50*GdqH< '30s~4$$WD #VU׬<].B^HYWo!8Md2knFz~fs.yv0W_ێ[=yd_>|2k;"_g2:AղƷhďwo^jC@={nAo+wym[̶57c{p9YHo-!s\FMkvT*d,9juc!t;)9⾖Q5PA7*G b&9'`rb\ ~ n7' RiBafvJ׆t,Tq(S $v/QSPF(T !ZΎ?;sm8'sׯ_xљmq r- t:e2X6 [#s1V4&8C>6nak#I( &tcÊcߤX,<߽TkBs9yHuLc6-qAAɫG; _b)A !)r*ښS?*ضGu#;E2hR \ʲZ2کEebʣzH\3FO-ɗ4zKT3,4 5 a(B1 bz<E]\yYx yg BřYXT*]l>lUK?_,~<^uWVVnܼYz?1.^JgcGVAnl6[Vʛm=:)g} ^49}n}#{4[Ù..ʸyj23̫BԓdfzX%cRXlVe4r*lwZhd@g*SQwE4#Y 1!Ͽxzzm^©yfYF#X9҇뺋‚2ҩ+`~6߄33ЀV%^GWh[\׫:FV[]]][_fCwNq Bff*yk SqFN-fs0@0氣DWT*s\gaW4Aߡڙ!ʲnHl1j`:n ={gzt'NJjY'<șRHI0EZ! Ifw:ۡcҔ|S_>=.DڛTlJ:ɝ)gi}/HMcUl謋Fcio>t A9~7Iq-+]=D& R+a{=fLhdxpmB}]1җ2G; tg0fC!23Rpn4Ǿtx?%IƴĮH#D.so'cZvEa A9RV#^ yk4=5R8,//OMMyowpiiiye\.w:s^)}QC%\~ȶNyr\5ϟ}q) V.PDlRTXCa? à'M<䓛~l6_<꫹Zgx=ZRM#3h0p8be65r壐Js~qZk9h@d Գ$|-84SFw'iY=B ωpf2nlWh4pB:k|/ ? 2͊lmAꫯ 6>H!BzTx!61)ztt/lw:wuVL6FVڰ '0iqfb;z.]h6{.Drȫkkx^@xa*n޸q lomU+ z@8jA{ggG.\r9"Q$V^Bᣣ#h7|3=|5m߯jJxWAMezz$ƒAcTfUׅ!˜8 "my &1<; \e@0xO0vV633w`Ȁ C@L(ޅWcr+#͡6 ^aHm=ThzYz"|1캨L_q143f<ЅZWIA9v̉V~8^4z1D3P,#z ȇq*fbVĢ]јR0ԢQYӊT8MyT,PZ.#,H\Tdʵ$'N鎝: >ɪa:N$IQtF&BVr%)wsֈÀQǷkc:\L=U1ՖVbJCBy: AwIӗ._x7ӅRݻ0ݻwT*^aPp\`z0?뇣?^xm^8y:M1cNٹ]˷/,4 Ƕ?kZm\[_򫯊^wxxxʕ+W::?ӧOZ1!*Ô~ld#ecYJBaaau9յ5۷67Sz0l5НoTÃa0c@2+oJCOx޽t;4*_ .X2H@JryX>d TZý\>KڪX((@{.4`[nes9]B@@y(xT&;weu}mf2Eɷ~ BbV[]_W_B~F\6 BxիJS.7ry>'I+!B‡w].\4U.? W^WOީ r8ف %!~giP~qi >|Jn0yvMX$CG`?堆D`ssh4Sz*U+G/M`0d)AbZѨlJ% ¸t;#I+$'o_M $p~N8ӈu K4#1l6P |>6O @=Pƌ/F1P' Z Q()46]CI3@%PTlz=<eexDA@DOqOAާCUe='kkI/(n0t2ɗg[t2 oq._EGi-9/2TK*HCZ>|뿁`NʂrK˽|}˪sye J@!G% yx8ziXC2m 00ɐwKl"._^XXX]]B߾uÇ~N%ܻ|K˳ss~-`s}~B٬U{{CD?)ma/B n/Wn;( ͫj>,OO/..^|=sss׮]pm391I@p1?Nd`>[]Y`}q=x;z] pCS" d+PCPe"lۙ)\x,q*fΉpV+3L + 6cyC (utBr|ڵ$2J :֫ǏU1kړǏ޽q[@a/p7-ܾCصg2ϲӞ`T d #XC('*ȹ]9:\P,^p_ ˰U;(AB`>V,Wv3D+/E ?-!(nl'w0FWϪh~S|27?f>ϟ߻sB.4N̟Ƽ8@j6;ڠ;wVVV> Eg06Ah9 .?5}ݽ =B &IAX, FC4&tvd"gar9m6== q2Φ@E-HrT tv 5dY!:Pl~~*|>eiԣ4Ţ1oCUA*Uȗ W.!y'Eie< `ѢG{KeQJ&^ |EȾ#b z:w$M% +t?ߔj$t4%y 1+ͤHB%stU92|Z$P+$dD3J@Rkd)qiT~؀ ofeA%*Lo*MǎI/DW7/<2Ǿ\2m \8{lI9KVе#4$Iٍ"_'ca;" ^'fÇru]+nkn7;oתٹL&q>22'4s`adUUzEXwGZcOv:Ek) jRՠZS{vR^ۃͷ@G\v:<~j.{AG MfΡP^fydzWî)CZ۟m㏰bl A QK[ٌji$D;1k䣷;;;]fE咣98?qy򝀷8 oK? zr9 *B Q9 x!< lXבJ͕uÇOyX vUOa2iHɓ'— p={4 I!L߇`D- Bk:fۅׅ~ !Gu݅ ԇQMUR)H͌`+>k-ājk;[[o &kܺuphd%yŽ C%j$d?763Aܻ`ptxhίC Vl#ނKIhky:RBU*n72ASn((5:r0xו"ܦ2B!Ƹ$y&-^NE$qT%\.@uR:BKjrF677M%/ h3 ˤbވ` 5^N׌K1IOA|> Pޜ'A=#2.;&e`>=! s_A=?NA>h6a\suB"ľX)JlJu%iŌK^61hZFŊ53k"ɯ-I*S!ɦ )v4N3+1 XV9KlBb%#mVy͑%#6"F%2Rkܹ Ғob A~k8Q%R%'].#q(IORkOP<->vNHmCLbJ!iJlZqR|sq^(\"1?gEpf܃B(u6M17v3_#)zV9xت[lD*f:67{ L./]'1N)y2͍ 9_wwwMB0LˡSbacvdN4m>yL?< ÃJu9^dS 8hr~%4(Gr';3rgy}.#v,, ovc?{ͮȉ39*aDlF*ґjRc"X vvve*2TŞ#{10jZR`$,~i4MV[_^3*vbR@92VFL.g=Aܹuk@.4uC0؄2|ۦy0^ͧƃab~Ggڤ.{ppBjDoYBAl?4 * BP. :b2];:ftmIct'0>LCcΨ ?мj;1.'&`SSS+++.\XZZR)\!fh<\mɪ.4~aaammmqq1%´x9WW؝rS `p45Ǹe,vw çQ:h)4AANǯ>| 㝤#R =|b,cqʦĈJ(=quUy<}eb,Ȧ5Aq_p)FC2.p<&KDMKtO1q;՝Blt$L9k=dT(1`bHK IIR-pC uC]k<*kq_y.wi۞Nނ( R&ȑ6l6ZvFe'4p3b-{|IJ;tsKs qD~PE(Qr=;$=i3wzT3eTIؤ''ɶkGe0' OmRp.CnБnʱ)4j'O{DQYyx 6"D!pnV$ӷrJ.JYD+߮vdL󯚄9_໙)|Ňc8үڋIpF:V )3b-j&sNXY|R)%&{.2٘a l>O39ļ)z1`"zھx Aq֐Mls׆ g);&Ö l9)'?mX]-{9g,ɤfm;m$F5D Z# =㽷ߞNӶڳ6Ʋ yx4'߄NƤedL’]hw;Eڜ6?|ٳ`W#B{~HWAU)V ƅhd$&SSS\.J4FbqZ td31oƼ[(R;zJeWSՆabKo !`6c s;FUUAMj'ނ^+T$vna&wX46lm21AkNAy *t#x"=$ZԎĩkLKdWe1ܗV(PȀHlP, 9T˱HE|RoY/lZ8-ISxv۲2J<*גi(xT3Wԑּ?F 4r-A-kk]qäi9>UȇfQJ.r\'gyL2=?öːm9+)'o3v]S h9^TTב!< vS賔2bQ$i'έ3%Klq+J.8AjFat&~2  vkT*r\EXl6N=3h>*u\.Si#jz^ނBhј&jh4,@Ufh*_$x7qilhe`mTcMd&ppl6aﰹd3  {FAMtZ0Ѥ/:߁YVSS\B Kl6"N%!SI/(c*uKbXiJ22!V4zVLR"C&FMPec,PJƎ"Vs+ HIL͚Xmt9`Lls\.cTq\162‘Xbl5x]:%[G0g^lcc 30:n$#Q\2^< !|E .By s,F]{eyJɘ GdL@wA&zM! 'JF.L&lT2StHr3M]۶y^Z@Pa aS]W11UAS#l6k7Ig8[phr$fnZжhk =DAxxjHHi$ {b㟓f#^N?m[9X%8 uEW bwЉbSPЩ8DT9R7Se*S,J窖Dw'ŻV(e W9|p32H1Hא4soOq" ɲr\'*S2SP؊̍dKy-,lQepy5=σKQm"stth4>_%@`1cp EALs$h4 )zX2!.;O'$222h#*q# ip<&o6q2Ğ~#nxd|Ґ5>)6*؝JZbvaD3XB5,˝d? !Z*fffF15$/c?; fe ƊJ,o y1F.3cc4yo֗U2+E2U1J 1")J@  N.ޥzW0FQz$Ųl\L6}10Fv5oدFNMВ~]fff^7``,m$~6%AUH3:5mIZT0/ ou;]Fʡ BrP1 אzd崛4(! ΐ8h)VRe!MPrԜ02j+aVcWwISN>0giBf]{ѵ?µ0Ͳ(i˚]P]?Q _$Ȝ|pr,o wSA`F~67ͽ=`|:.JPdm j颎4! ?s5Ch  ֈE%sV-2VaXHȡd*:M E ǯ|퇎dFBʘ|R.}Ush[eܲ)1ɘ\&쟙,i*#qغ]vy#5,u0C5}fZ S`tufeJa;c Jpu)m3Y1&<Έ6]fUʌ9!BDurkl5m+͂RcH%cRv5G]/ym(Jmoa'Q 9i7,~XLRtqNr5̓NJaha`Y\w10W*hLMM9|~Mr9XiFK- VƄd4A&Ptʈi]5lU#/ .hfsJ.#WF/l[&V;,iFRaNOOCLCXp]ZR*`PmلDb5#BF $o l®Gl[A ǁ+P'  ?ʊp<\f2|d[67J\.wʕ靭Fa&&q=B%2ec͡q x#zdLc Qɘؤ\&V]?j*AΌ'Z2;7_OI팝^D<#&_E(r]wqiի^Z-3AA>\lJ&ΚJ2=fIQ`0H MV}%$@UPۅKyj=M@v:]64 I8NsxxdLmLGBptt F+'2Fb.j5 .L IPecTc6MBW#1zA_oc~A s7fɉWAw *f>t2rƙB!L?_2&g3!36'"bVcX_ }?DrK?8lsN,qyG{~xv '\WWW?쳵uJ,M}ܒ6w9 8DbeëEL\blKh4&H\"PAuT2&.3cpśei Bbz #I*S_ ;NOMݸy|B *6BOdIiMV4oC59Jgfgwwwġcz c8`0c&Ccd+SٶuS6 oR/X90k& ]M^Y:MUG~GfsxV'[;! |'^uA< A |se޵ScC_Z0NƤ;25Oe,5"ފ8 #eIi\fg6(:FH&Vf}OMcV#PfC<1B_BJ}6;7nAJ'J۶ b.SOv\n~~>NZCxdfg3`0T*J)Jl< UJ%O>̶s&6,_zu*(qPw&ɘK%cZS2/q2(A䵘Ibxƍ+W/H=B(|>zNQ*Z:!@oGGG&EPPùV@읚v'W(@>== o={looMn /..Vq{o H؍L"@^6$#cIOHͶIR֙w9,{w I=fyrIy9I41jWK&5$}I$I  AAȠbA>0铬Y9I>V#}.3RT2&-je"i"ތ8^r%L_EpKQ{FgzؘB :YFMP\ x+BL8A_w?䓕]X\MYUey/^|p8lbsssVOz=_(\zƍ\޾}ǹ|2l5.gRvjHulvto| F܏h: N67$pf=7I(͑NƄrA^OL^|G}|ɓl6(߫lJffgo޼f+*va}}?nscʬ޸yҥK)x^=ۭV O>w~a!NC\\Z\4}OP`Ν; Q 6yXBZe|3rlItj2aAAA䗀:Qf+2F¥ՉUhpdLDe3!3[$kE|M}s ~pQy=T6F.c$@͒'8 @K5‚~T.߸yvӳJ1۶SSkkkv}pp@)]\Zό˗ӟra.9;;, rx,Ë/Fӏ?nmnV* ._W_r qo7?$4ЙdLJ.dLo .dL1R)L2'J … 7?4Jݹ}iXh1!1!`.,,,-/7nsׯw!*zlO3 yt:_axBQzJ^ݫj_|{y!1󎜫CqLrF36?q,` ZjT6Lne b%=:OkAAAW3!!o%מsY"I(C9gLr+ى 0ɹ/eMݱr_CM)rqV5kzNfIJ^ď~5AWȊ\>cEEۛqgM\nqyٱJR߸1==鳟~Z\z++.lmnyȉ"ݻj5J٩~gg$lZZYY^^'GYqy1)w]֯Ef |QR6ɘrrA^Bm|grg>H踮BhnjuUxPѣGLtŋ~8f_!P_|~G؀}եe;w~e!Cȅ .\<ܽ{H ; Cx3CA+$Ÿk>o#AAA*fL,M$9R& \^# yjTy̻/e\V+D)y3xw(e5GA="ii]gƵ.-lU"̥K^m-5;jumXN_=JeYxwccÇj++SSS Fέ[Ϟ>t:̶\r\n6SukÇ޽2.[]PR*_..Q2&rate]f LE AAB7J2|n\8p0p=/&"\.HB?{K/C޿{V镕.JP !r޹ E|8Bz8,ysss/\{tx,J)AAAAA~ AD.9)\[IsTP]F]E-I8 zӌL,gH Q0bu͌? FPC+eQF_O][[^| N_zU^4===j/`+uGfՊbBJT7PCq'2~6?߇XJ# mĶ lWݨb"r0{o"M"pRVɘ؜c(1Zlq|'q&AēqJϿ?BjRYXX heuh Fnhi ;S~!S8A^*ת[[fC a!6zd loZ-֧ix  ÉdRBxƍBǏDAAAAA_*f`]f5zD2[29fMm9J={-喵6ҩa,RVrJXҊƢWEF-+#;2B%8à )N/..BEQ&_B& [[hdiosFi:ȣvlL҂~/K   9/uJj˫kk3ӅB~AA2QAAAASӃ A˸nɦqm@ se%Ȓ묥)&C23B\0{lӋQQuٔ])ۆ JJ6}]y-N$7 c&J9Gg;333d :vJlmg2az|>/:v(N99j+J~^N]ׅ[HOC F1ãh3.M _ҁ6{ -LR29[%cJrA`Fb[7M :J N<`pбmq Blo7uce2<W[V݆R%v_rX!)ڭVoawT b?fwz.믯߸ٳZ oq(AAAAAgyc˖YC#a1\GLmYF\{͋2NƴPfsfOcB:|" ̼KȌc/TjGy7Rʞ0j D[mRmJ\Jt~޽{Fc2%d(R׮]q\.7=3<3;+\]_yal uylҮ- QG[?@ou $2l_=zpjzf}}}nvvqiP(|g.]xw*f\F%crc ɘjI:Omːe׎eƵtm{~8rƱ\&V A˦d2mՔ32o\C_@ F_=P1X΄afWVR4 mňh'W'|2Lg2kkzX\X\X__6ٹef… \n0FD1bnze7(YK3CHpiwjjph&u_\ƵWVf4H'c x5 j FeK"!0j8Byvtzqq˯`{>}:鬯A˷/,@i*V (!Cp !BFa}"!* ݞJp{sdÔL 9R#lAAA9җ\G6bASdL.{]F%c"z0a=joɊ1k);qJ.#ẀY^lةvB)00P=l)"Jpe-AԊD$YhN)9~_ S]qT VAfo~IP>sNO?Noܸǰp0ۻwӧO,=nWiWucc#/OLhW 8DX_kF#9ϖJ.V\@QaǗ*ӎeA~a5x}B(qhb* ]*庮JDz6ݻHa+E?|=|euuqi)z~w4[b8xiV@R.] 4(`G\)J ӧݯ7Bp ZAAAy zG=AeL2&bMaMq^ ˮ IdLcϹva6!M5PF|9ͻvfd$d3*oTw 7#eqη67{h4UĬ8L=j`ptxXTF!j?49 ^* ,Mx_w%q5w44 -zW펚Ai0#dLtV[h! Qf бjd5T*ny֖(/-(|xpi,>y A °h6u~KJX{~W nC<^/I%Ulj eAe<^Lb-   j~q?A wLe2ñ\bzڤN٫&֍|k83:S Ł\˜/5Te)ڌSV|LGZpcd6(ZZՌSeY'=fR,UGڶ %(AFvTTQF ! }ef9SV t⤜% F{2<b[pcq=NG.a 91v2^o0X:I.ɱ^qΝ((c`nloCRPGFV fTⶒ!6ѿ,kooRaC,{Ey1lq« AAAAWp --5yNyςBl@\{sʎz҂˜疛ЯFƝ6ce%6GQU6%geiI5o|/a_-v1Y.!S9ӎm7DӨè=i-E=vwވsT*_(x Tss9.`|S55 !TΈ L2s-n:vg _,:tŵ2˄b'Pr|AAUN\>fpm2l%0T4ᆰ~uJެ"ЄJAs¬2*(B AAAe? =f02؋]F[z[V+RX.sdLKfe-:aY#%g Fa%(!Ӷ {0:Φdu"^ v$B)+  qg}G@ @ iA2Ơ0b"J ]&KRT\ͼFnljO- 䛵R42Ea!R6| ~0)+@tJF?;5/C /<O#\t;Ln$",eP]rH=`uG^oGQ@ |(7pk]:<efrP@ C;r]B]ubgM5ԕe2)L/ \h)Rג,3[t//!VnJNy'epepI{3I#OnB,k^]0WeNtTH!v%61D!@ XF]vO22Uהz˦8Oqi_L@ [b *Ͱ@" @ @ >ܕ)טAc&v?}1C OH<]&fLVfLlsj3&K>*=͵jD˴L~vl1RpHƁɺz+$ ~I+9;Iv̒ x4y tH!q eWAFWQ^]f.˰{߹au@ d:F_j2tM @ |C|>Q/ރwI1C X,R ts'nԫ]Y)~ZJΛ1iXH֔IDnJe @}l/ս@>thF> 5A 2F77L93ҌI)B͘f3.C\>n}.B U/J nP4#@ ,*c@xU׼X+kc.cnDitY孒re<]D\MH_U \ӌkfGhFȡdsfLz:J[>& nq5ټ>v FC4&D ܟY. K9Os4 &79@ @ |w Ę!)¦"j .s.R3=P3p/o$r4.]1ee)f\B$'hRƍ_^@!|үQC& )걜 4i41k{_ux"\^*JO=[X\x8omn~wNgiee}m}pp\n4cjJ4V9]&\nt̘uc9G0˨"ί#m6=]Q^]hжO>][[@|}Aỵut+HV׏΍,xSy%׽B'jCΑȻ L$!c%Sc[ڵIl !tuuufff8$b[::⣏^xREQw1O>_:KjZȘ!f^'E cmkR2nz̐zKY\̘sMcE?o:3vW( wߵ['' {W_~yzztpieV"f%G3^|cdV喖{~U[|Iq Vݔf0L X^^Rtv}rz_|Itm@os~~/~F2WϞqnsc7_|%v{6?jb=JTab<Q2ރ H9(J @ @ <$c@xG.^%O^.v2ݳv ]ƛ1=+G Oi{ylQ]וxTmeް0MRł-rpNH9[nDX#ƚܭs"Dٷsk#ŜGRB3~Hj|Ȇ{{ޫnVIeZ}gQ BĐX*99]Fl`nNYfLrN(m0c˸G|q6fsV;;=/iv9888>:2ZGQjR}LY~ǠRes9#%@[v҉Zw㒳L7-5M'7|sӪ-[c-@[$%1Cx>}`9?6rz! A,OO^/Rˑ'<,ɐ,\|K3#^E @ @ <c@x(1b/ j*fe߫t_xFVAr6+L9˄3m6ˠ+zZRMtL{+M8ZT, {z7ӧZZ[``њGu%$p <%Ahꚹ̹#?ǵm!s~$IB.~uJtJ‡@.ٜhT 녮0c4e V fz"Z$ .S7hE<^7?CSmdIEU-WqC?]0lHbY3^G`l#@YWB4te}]tݿ@[:::ԖNNNG?'˗/_ug{h4MOOOm3mˑt0-G G`m'Lxa8 ?@ @ ~3»rɊXo2vî}nz q{N HQSI8 mmv~YtFBXU( 3dlphǫ=s\4 >~/ dǺ1M: ̘^kOrR8lsI,~mr̛1 :CHYbeԜQ~y̫DB$9>>ف^w81M=\Ke@[y GzصY9Fq?5Rf w]p횒<.Ɛ\C[-A uWR4eP4M[''/_O#ECq,Fb3&<*̎`|lLbaBy|ב@ @ ú3z,U睼(G,zk$Y:>c2HIHѬe|st@Aqy%c2Ӟf3"4᧚kQz8K2[qۦ$\ɣG`8w&_C6T"e`NQ`M)Z&ɤ1&pb`F-].23ar`4SlzZ17Fj44|\Ѕ\(uh캀8PbN̢N`.IT!e4u!Dscv.9!s 3f[9(2Lx`ZM1@ ,_@ @ Gu 1f ̘Vct)! ӯl'&e/򩗍3ve&upܥX 9:;iv?=yWY) 4,H}ev+g.3%cגWsS[8pW!LL\'ɻ@ !"0r`"0I3ȃ}5Z5F2t/-e$3"50cg0c t`c^i7r@!AVeCk&{<9CԔxo79D6U|Q>0%#h8ႶU?i)'.9 q0R:yaCS}@ |OBz}S? @ @ <^~' 834{߼bo."Dތi16ux3&72ˑ|᭝T;f#vV@CBJGu9S2Νj$vŽY%ɨ ܄bx}܆3 z )pMBDZזpѫ^fB~1C׮yk2<3DyFcaa4M$/]㇙˨F afjΌ7Լz氺]&uô[gf4-\E" =pJĦл!t}!B-ya :Vc5i;#D)pȾn'R3-G%rb"<^g:77t6cw21!o2Z(Ȏa6*L~R3f^4@ H! '&qՏsOlbo*8ZT%8gδH4뚁d.h% ˼Cni'@HQ-DII5#'oc}-GrƼO(N)wl+7Z,p¸IhB&WۯN(5FI'?I1n_ˣ#G˽o6ze7c9 @L!'9]L:gH[}h0l_biq)t{k w~J!ޅN'oX2Pm׺$rsqJ*QL{֭yR~tJ4I(OkkOshggg>~J/[L~a%F^c'~Vx5`H)4#%ay0@ @ <[dW|Cdn1>~I3Ę!B" 3&\ e{F "7%2t܀.7L_r/bQg^fv3}~UVXPJz]Q{I\2pfZ_L YM6P$Ieᅦ+`{C7K䝝-H.Z^B"/%@QG3>E{hvA%hUiZE£ R)96fggYS/2hjC >@2bȒj@ @ m2÷=-c@xpMOKY)j d;-]zS\]&|Y2IORj)Ru?)M8nEH.FjF ^^gM*M˱I˳E>x|[KEe91ibk^uw"նG]`@PWWnber,T3@A 3bMP Vs~4~8r3]_]!]fe+όk2V ?Yvϭ`Lp@#?c{{{Z[Oޮl+n11*E3VeCy'bnxYm~5'yggg/8==ŶM߇/hXC <76AbNwN7>$@e<]&0f9z=@ @ 3f_!;1C arM'y9j*xnlVg'S8_r{\̜;eЌ WfX.E+PkNI3/cǮ94~z^#j@wX>jiblL,9YBrV=cbK)ؘc?'fP qk#S78δ7-um_t UWRLiA[?ZֶZoA .6M$I en`2+e6o.eӳ`ƤSL{H!s3۵,tjRre0|GK~9Kt<{֥֎3ö6^g\jK:%XGBHAKk6dY&EĀiK%c{Ox(x-eHZ@ @ >ۨoοwJ1C ̘R4r c3k3{n@a˱|^VbfL^Lmٹq9%Ŝ+998eT復O~T5|损dY dDRZeT{6XkO"#"H늲]80fE ^"jHơ2K!7KN':=]gu<tZ3] [[Pžq#- z)_ Yhձmm[FΥinZ-oOSOOo O͘@:ն]Xscu'lz̖@PC1휜GjMJfXX15Clb> J-D8`AN;1D`' *W3Dfsl|[ҝn׷#gDKtG7߶vCoۋӍM#ˌshs9 l-(2r3 e@ @x_6qB (/u bo_]=+EsR| %2z@1+x9RHHa̫4NfL"XR8BlXIv>%Yp5)Vc9g^_%~pS8E@"XrƦ 13BT5~",IO! 5B&nY`߹̹uhc4Bʮ}Y֞1cNɘ,mAOdc_ZN9?5vθh`L =Ge^ N.p.db'qmi4rhKmP @{F^`^Q^x\4Lꭙe0fڛ:&(hF]t:{{{;;yv6lm( _6#SI Hi-}@E- | #\ 6ЉNmeDNXCgII%d5q] ."Ds;Inʶz?v:Vel:]NnKƒ^]y/ϟ>;_m- cٜQFOxξfNױ~@ @ B $ZIc@x.47cscQS6Îbz)ZeU^GkT[0J^@.Gcof#q5HcJa GD-φ 9*12.;U1C<Ƹī&\tk]sOU%s=-,45d6 H\`xIMǺ7Qatieh|"?J >=9I}Ikk2c4XeڒeE+i"a]H.(Y2g̶CeB&GgmufG?14~h,Ŭ1o 牆W#VS  R}fЗWb%JV01X0H);w,; DeQkYV\X]9.f4&>t@*miz_j K+i͗ ^o?=>:Ϻn 30dک%+wOX˘ G\rΗ! @ @ koĘ!n 腄ZLAYf`ǀ$eR1NF{RVr"(μQ9c&lF*'.`VB/8gG]e]oHdE76kmfF@k<ӨОUEf$ߵ@77̞:3 -#|%sݩ57;N'iݷ !41\y3ͲW^?WJA27hvn[GO]&9wlz+end1mVXDkO9X33Lb9գ~˗N'AHkZNS DD2?@c'Ojz[ѐ*x?`{Sw;HuJ^AʏVu%Zcw\/Ek%.#=1kE2#?^1T1 |%guk{QwM[:<1L]̌cix_x!G(`7PxX~dy =:2؛l z,yHJf41]GQA{JI1Wy2*eYBmL׃^ 2YMY%f,KzRw:/T[gtRjG10x^ Y>r=H΋d^ZTl W*fiZ}ʂ@RQRń6|, *Ĭ~YtJ39C %؇3Ǩ8uE L^ @ @ay1Cx`p:Xr#@[t f$g^Eg4( Ϙi*YM7 r G+1s,9daJ!̆I ;;Wj E0vLЁ΍i +IҐ"_f:,Md1D:$dzp:cF,$9h?* -i.pps Z6x"]f^'ZS.c2.so6AeD.#@A*e̜]n-4{@U70mFr<#3f^%Y8_BgRb@6?^On;zhp5$eF(0s@%q5zJOx HjqnI˘i,@ @ cO,{/re";Ŝ R8c#I/2U=Ad)`]j`%.$}#ڝ Q~Y@qDnέV1\l^ڃTgoGs,JR JyNN4;^m۽dw*6 7MV 㮗2cЁRU̙͘7"1=\]qfvjBjLfv'ֽK9)sA!K[e2 y~1jJDj>;$~voˍSzWFBw;lK} wjW&P4M/vU_+A @ @ SuA\wKJ[t.3\'QPFwǙ[hvL,K^<𷎝y(d̻BgxHԧ/ul'Q9./ޘl-=buKχgwOF `3e".BW^ S[V$YfL. 3&TvO3 &48 wo||<`>28s  ü6\/E cܘ~2߸r8xwU["O}gm=(@ @ A3'0^$W^ &MbcLJפH>N!&f~m_qtMdba}Xjh0bMD àI}@x!/@ @ A-$ɹL\y> .w9U-MyH9 dDyU 1q)Fh_0|f$2Z{]gPY [m%z/}Ĩ59` XEX%ݵv7ѯ PkR)9DɿwJ ;C28z~ .Ճ8zVfEnIS8z@xLFbfLȇwnv:0cr7.SplNe(<,0@{hez!elMBB 4|VJ*xyj+IdmkvN @ @ C1f$$1xCz*8AE9QWOGd"839cDY#rS.\E v?> DWZwfMOVYy2YX&[PLKΝj^7/H5`2ˑKY0$e?;E_X5DfLܔ{d/)"Y%ѝ w|40)4kAv;ue ͘090`M}2N`CTt o¶ J0h?.XDze\.xM_#`K\߸,r2X" 3>V|)kqP\8`̐.Ё._ ƒnz+@x3q&1ME+}Lj^ǕڳH[u]hT&1f$̹I;~_x,XąW)X8; o^.e] Z-Da~f2i!30iwQ)3~DؘXL#{ B e+Rr2s=Ρӏz/MR>fL p4GMt>uab ɟxj\0cr۩n'TxEh@h ۡY/*&A^$Dbr}m"ץXdSI$xLFL~!`Q*C |ac毋PLxR`v[ NshfbNH) {R@&wLQb˥"< grLY!S&#O'>_@4<m3HMPyqx=~7#;*'HEYU\=t ].-}a@ &FbŸ WrɁf)?a8@Y =:*asƙ=cxS$3 Fդ/@%!V#/`ٺ짺."aV9HMK_VaazV]pc߸}ac5Wb>!]qfv24$L?Y_ ye! мu 3' T]@R'e^Ѵ;ܲZ!wop8{oڙ|#+vy@ o}`8 .@ o"c!"`8>}.N_dzP88FEPqڱKB#{~O3N8Uyo?)y uf˛+emmԤx@ 84-m6SXKY%e?NgM: 7ErjšPs-{~f׹} Vseu=)eb2dh\o fLi.#r_!ęiݐ$22E3ԧn_6q  JC7M)N)-aAI ]1*2~N2ƺa3#?fw4pf/5D%Y3zdK{i^7ˁ1"HK/gP%~(9+JzN/Lx$a 30w? a@MVx JynM Uy[#x`Ep*2\Ȣ \tNMR cgW^')FJ4fF U!"1B*A /|Er̛1/ձ H@Kil]Z/&A%A|_3,a&M~n+2h9q~S7ڳ|>Kѓ)ΆHv\8j.mmvS eÕloyp" }_Isz)p2̮r&e]uJI*C!JvU_T*ϟՏDZ_[)^?/hO<_zTj$''ё_ V2@gj Af?> nI]v0x9PL_r԰!4HG?^YYd֘~^hZY&4x& J<-%%*Ⲇ ,*A"y~L*50p&bwŀU.Ϝp09nvwgLV ϩ kw盛z'?Aa_uozS½YV_@`яVVW(?;ӟn0ayjmzÄ7XZNx8/Ci"k>,E.𦦈H @|Cq=3 ݤ`En8gY6:XdZ~)Jx"S<N#Dr9:N*xnj\U0zH~Ϟ w o@ v=ɘI-nWsK%^䘩|ϘQ3,. WӁҏahڬhqE"k'e!k X~`̤cx6Lmb.r?4ȫ,GuC t,4fL«ˈgb`Ƅtl[\i|6ԓX- uw1|{;rL*q IW܌+R LptghM$M ?k50TnZq'ɓOG!) ӯ??^~}$b@E JfsSf2HۍTe7~,EΛ1Ԝ =Pf6 SJ'n{鸜% utK}ѯN>7VeJ/C_8oDW gJ8Җe^u f=Wx>'@1?aʋͩ`p u~XGTpqbwY{l؞٘ZAd{1&֓&d2fLp].2'E>+#]F" 2*${?h)배}.T k.Z9 !LoYk0EEG1K&Cf5W;Zk`n?˽~{{J%Oo1zxh6!䰽!7X $ڷ_2t"5 x3&?8|L͘.sc@ /W*q~W[[[4xhޯ^tL՗_nziFpp0@U\TGِQwXs6I { &\0bSm3s~ܔץ\IV^.̔88wGK!R¯_i[׸vttoV_|q5Տ??4$I Ę! \?߿|0lnlO蜟믿[F狏>jm۝q ~ýJH6ސr5v]~@ +govvveeemmm~~lMa ˆe C MPRFQS'OX(3OqA:6iHA-TZ I3,vj頊 Lr`G8 H_ ~spct榓Ӝ#V/hnnŋl?I]H]4wYXJG< ;1=d62)c=d,7ȅ@ -3<^1~yY/=奦<'檑Bx},2=ʯQgH O'!_8H3I-f3m1OWg:F֫ 7vlCk3^^]湧Ȃwn?/l/ vHlZ|$upy3ѯ P|-Tʋ̲; ߅,,t'Y9jx?hvR%VЛݗ3Tb.JB\l=gkފSam<<<<cif.×XV*:g~b0{ B}.kĪ. Y,ֶn+D!8ǟZg6W4@RB~_dY_|O>ٙUy !敘~Jݼ99#;9uxyAx鬔 JHt,;GL+ ~+UciLގK][?ï$ nϴ9DW^}gShT;;vhwO0aC COF ~0H\.C#ߡ_LNNzc& \@ki&b0/j}mg1RC>SDE6*x3"A(40iD(fgg 5_)b$xY/fήnGTXxʋCy9&(w!z#;ꠐ8 2t{fཐRX T̾wgj ^|^Kz~͘U߫Fx/j҄#AS|$;Huߌ#2'b ^I4mxOf~ ^tSbe5Е$Q +iƒ4MѪz}m}}aqquuJgǣk(ɺ'|̓A1 2},J̩zG@E13pAF~|| -t݅啕0HkzWXĚ,Aԥ$DѢwRb> rP0O1=[u5b)!Bˌ b#4$\j: ^ kL\.I!HB;::B"+ӟk1C]a3f^Or+gkfsmmmqq(JP<%~ R X@}B:jZ,w8B7"Z*`F UlŒ>a!5lb1={Xl!(*Z/JΠlZd`[ FҐC(7 1\ #?H_@xysV5f<&9M,GKnjLB: oMt=3RWޯ3E-.8+U᭑F>a0#,Fp^pw_l3k c6:ԹquW1ӵkF i27V17ш*o]׶; yXnD`Ƅff'NTw0c½fLLe`Ɣfތɘ" ;B#O]\KuK\CYPr=s Vj$r؝\E1AFXXolM*9܂8O< 7iKe!|6r,BeXp$\]eRueܐ̓bȀ._eVO=>wpc+ |׫jRr_:̵B 5)Ry̝pyEƹǶ H!T̜vwp~bc(sΦ.&!*en`Yyg=bIZ- Bh7 7~N0Ä,@cqNN=Ԫu%3H BFKeW,lxff^7 t8V8'D L YP~x!Er/C R–R8FHN2@K+Nhj`(x!; HcW8oZ,/x~<.i0۰s%J+ ~.3@ ;oeowSޡrq%*x7ˌT^*Bԥ$B+ ;'؅g(hi;(pӢ1{i%PP^΍wퟌY%jU`R>LM.BwXjHsۉH2.[DYRʡJRf( Ɣg)@Loّ625[ ~WRHyI<~DD^UߎA"$ y G |0:(~HqO]YYH2j !}OE1 Q3,<|v#ޞ7-:SSre!M+p34'>[5M .b*Ue]dxlWy3:@ HxSԋr<<ҞS~Yr=ଂ{RU+c}>L3Qs¨6\N=Eč^˼rSÜKy?NQMpxWR>-EʪtNVůύ C ;]UWhՂSb~;]2H P|1RK~y9f/ǙIݠ=_'Ed|)n "'չ[^ԥTQޗD([cbuUתpvcܸ8nP-80jL8 O='G9/>,Z;kŵƘ,jgϞAL p?'O?tai 8a Lj33 GZ2ʋz#dܿJ$@mOZnj|okk z=dH)(*ˋ~[yԤ]` }Ӂ!*_B OK8Bᥳ( wKKKkkk e˗/ C!=v8#Ԙ).L]SH =N~dUVpP{qi"2fd-ow\qrûS 7~/ƒ d7 g K:=y7twF wZ|~3ŨO*׃&>'t x_$Oj}TmǺ̎!Afvl԰SljG:ih ~SgӒURfFof7ى6f:u'[RguJ}V^.ˑZf( o% ;$s|wNdxi $Ku:~ᷫ: [`؝9 JB;tHMΛ]wykkɟeONO~۽=kiyRQR~(˗/_zA=X_8e@])uΛ1%z b.Q 2IPk[@ьpW@c6pl?{kZ%҇kKJ2t'7VlE7_ڔGpa00)=~Lڸ т#وD {mnU!/'O'RiO2Cbz |~'կ?$S½Ѐ??-j9L/~w|tY5V|utys523Ȁ|&x#(0k{ugY|܌ujH9\U o>4& 8(&e`mfgg_B윟+f皵Zmssw}͟ 2]fC ' &_չfz~ۃH/[>LxK.D >Y øAC"r1!WWWapW_mXpBC8 s'ȥ Z6 i霜@[C*g.y*]s T>'a%dIPn! ~L,kh>AWfBT o'=tyX >#x*Dnߴ#@ V _yP%<QQ/]iL' lxԍ\gnLp[=~aMrJ˰fvnvfzãDu^ޅ{CM6'qD Qf OYM ì]H.ŪU[L VWbS,xMj`@M/8!멿Cݚhtzmo>LO ȍj-z1,Sހ dXAwfpm7: \~o$l5VEq, H}`ƧZ{?-5㳳36&_::_߿x|ْbk|٬T׿˿ǿ6tq otԊHތsH@.11xO5J]}Օo(JW?l[W ?j1c]2s'A'FJiyppm E!͹?2] ƚ_?Ne>~B~_6$I` Ȇ& Z֜__O0h;]Ϻ&^8G,s,l- BO"ùaVO*j8RU9Rf׎}+79ww|Lblmn}Wrg?Ғ X"kK}>C$,r>a{{|}o CU-k(7~x?LF<] KPVXVNAՙZEʱ@±vG[R[y3L)7H܁H 9tPenaVP$q-^/H 2E)_ /28hF«HcK(LV%E$x]< S|^.RTĤ+Z I!Jf82SD̦9?DRQ{ olnU uI|@ In=CRfNkG>d]t91^Z)N3^ƫ^$/S~駟z~įU.q2Kku 2{Mm40hHX6{MDy{He~1ST$=4?^YO=ofիN6x eAZsPEʜgΏlÿ0gLpkO4M`>j5喈ge^Yz>4@qU~295e4v6Fa]l~gjOZ?~mz \,3o <1ў|?|o7ÿ+v-2 rv s3G LE6$d~, $i$^U.L~:<<)OIV ) y3'䙆1LPĮ \Bjv/PQB6 Ua3<$Rc\[]YYp,(&r eZsGi PT qQ?W/'aAbanL:;;Z-%.//C U ;b pp677+.=u @#D3|&;GS`HL@ CgQg\lF>,iLS[|keqQ*yn RT!56H${q_EP q6֛]4*S7ZJ3ۨu=cR^ jsA\ϻr .r]K<"XXz {&Zo'4nR$H&08S3]$*nZڡ_nWʫf:vGGGRZO>_/'ɣHx.g\V2tM2h'7+yP. uĜi@}[On< IivG}G~M!}+.G &<͠Ꝺx,ʒ3/x*yFXB$CL g=eiLU0dKL?Bc'i{ț$g{{:ˠE?⿞w>Da_Aԡ fi9<:<9iIjڋ/ ~?~5Dױ玴J >,'.LdҢ&@x1 ZJfggFT*XHCA$qzcmm?Q $r8!2YZZZ]]fYV½ llaV p*<\rT|{4P>WVV lP Hp)VcZmeeO>YXܶ (0.3J3EUiPfEz.qXNT*PQb|G̰vH A9"3})RB #A'''keܳd›ܿ(h1C%w2W CK@x7j6Tdq֜`[^}܋(Mv ^ oߎ1Shl'<U2!|AOeVv\ixw&Ԙ =nso_RUxOgL@u9gApB:^2}n@{ٮ* )wSp@+>Ev-W=[kp+u{N͉f:aq@|Cn6gggNo-HhR,+Qn\6l/ӫ˸6tU~.cVj3}j''g=??_5_iJe3}`5Ca2`9Dkތla`N7oaֱyE8ed\KSm2gŘ/q攊󗙜q2s Dt0q>&<ɖ1y麰08:af|dR~O_Wgo2k} n?/-~)˗/5u1 NVcY^oFLx ԛIAk@ .)) ULY.WWW{j eYR6QdZ"򙙙Y\\|$HsA FNIxc~&$h`@>pwv*fB [dLO %>^W櫯'F-/a=`=<]]O,fLv#Dqyz3&!r= t^elA!3t|x[w)u8"4xNNN୵{2[cJ̣.̺_t~XIY${s[ j0v+K0(9BJM'y9G&oB,ky >nQuU1vะ^ĢNE (.@c~(ލ 0qX0NV F>Si} ̄? CLv՗_?Y7loc5uE@h9Hފi>̼$@ u`)J333z"R@`;: oӧ =v:do`&JIdoNk!$?{1'BO䵬d)`RvGZ2F(6TZ\\T*PldҴZ- ݋7DKL3("($'XgŋgϞ^~Ip,Ԫ)\1u} 'O@>$A]8Z#_p,d`!+-u`* b@l_#<;DVQ>ں½rۡ/xo\ۨJސ27ZfY%5'|dI㉻9n|n4"qθ ;7mHw]K0X0Z)SR f'Zj;bw̺.EzV [8(K(mm3sꎱ㤂UJ+Y >Yz/_lo MG?XK3sdtSf߽3CΞdĘy3bMs~ /DNHir5&GGGj߾xbsc:>;;{L˽%]fYɂ.30cJ~fz7`o@UCe"A+^k}Md&+o췿O>< b3 Q=@J4"{9c\;~b\ ,K4ᩮH@rܻg,3q_ezVe²z{0ك,K`}Íp9ퟲs JT:89<ֶ . aq%7M_Rժ>tooi1Cs0a8qP$+& ' ?O?͓Vk?,g3艩 6(*!hLڹ#<HA@uCVpP'ko_u,zի˴$M?w>Ypg-~ow_v;ۏfk/t^]dej B `LK͘evN!q`9wblE(;6?R)[֋BP.][.$ս'vf̱60?::+*(M$gz/՞mc<9Igh3cXWSsk`{K7q9 j l?^mliд`Bqlu٣Kh@_И*LT B>3$P;@ @xKPfv%ܜzoFy_湱Eq&gxˈb"%_lF'֠CtF@Ýi2Iϯy<,>4i++ƪ|.ǿYm3k*>T@Ob5#{LtKJIb3$lVzrU 3fgڗBݛ.:xv7G.$Ahpw v2in|j6!YnmnBEB?=9zZk3x?XDH0HhɄ촽5q)$v3rc=mEY1}Xgxorl__KѓXԀ.^]f e&]⩷fTx<&UjtR:ҿ.c?wGVP%˲,amNy?mΛ9Rlxqf5֥@ /ܑ̘@s7ǨՐWu9AVSt ofޥIR<3x7_u 72_pa>76j<0Wޭ=|)T r;^?j)$CTS3p5N@Dcx,2r܈;Jke`ޥd.4Kϡ67=a;Ay1@ ^@sBB!4F̨A&:(EVKA&9q1tNNNF=C *QzH1]5B!YO 2&|-Pf8RR*d _З 궘h!qA BD oc%0@xCS<ȿa<cKgyk.a R^WIfު \N|~]-aoK={㱙3c:ޒw7V|\nh ћiFTSr.؉1I1^ kP)g%O 3^]ed:R%9Ԃ^T*DQT~ \zs9Pכ& ~Efaр-ZM)[:}Xer8-T΢n Q_XXXZZZ^^Vd㶵Ngs . n[˘ifOhLM=uЯgLɗʜ-FIx<7ͲWfL΍/qoVRkJrl'|j&xZUB9z'ZhG-@x1ȫD)%kDuwu7ˈ'\͘Eu2c"\ mLʪ|=SJKn5o^6+%{';ƌ<|a/ !!M fYֽF @ ¤9| dY.D?+9RgTj4J^5͙S{ ب1m> ƹ>9W1SNdxA->  |» C7à Zmmn!$;9'w4>>`c@)Nj@e2*IRq27XCX5(eR*Ct MU!#y .]kδOu[ M~c)8|wn9d!+e|e2ؗ@ޔ.#]W*@]XOI)e#dUv.2LUq^|&(@Z=c-LA QTkIE%+aM.ݫ~! @ 9o?o{M0/o".@Qbx؃ԼLWw%gsR>+E, ֿn2syٸy6,J,Y~IF?Iuo@|Bp8j*M)εr,.8e]&pXP9t.u7Ā.##fLˤf+3d=4sYИy# *E:gڴ 'pNE%RƇy0^w읪aPB 8C(-s7{0 l1cMz=4A93fDh433S2z-O9==EW&rt4MQ%e9 A\bF::x"H&*Y6LP'S9!tG/̤A PWhTņNE `eˎŜɡ4L)<;ױcE6d"AY WpEhsi䘻I LXtYr9.ڷv?5tדTܵ t%񓒪KYeNJ^'7JMXaap{jm+{nkcX jEJ84PdޏL[b&xUte"xL !`!b-{I)0Tr3&*􌻶bV*e"@f"NH{E3{ ÏAkyx)⼥zƵ cQA(q^R\x$D1p?gI7pL|R%xŅUK@ KhQJEQq\Fia;!d,4 H&JݮiJv-!T␲^7g6|{(RLTAI@qsss%INS>abZ}! Ds簅 CE!ʀ#9fffV#lRSAmC&HTܐyBVT(\";,/H3. ׯ'܏'98>A_Dz?G9n.F!UKr .&u%VKj9a}gA) t)RPw.ns7||ܟ%(Hٖ6A]Fk9]F=e=h2NK![9HɅEbFݠ^=^W|FR{ 5]BE" c.[> T C^&$8."FkC@ ;PBRjYq\sjٳF[z^FIR*tE&an Hy"8"O O(R[ et:$8 58jBhi9ӧOWVVp Hs|| D UH1(6d9@'j i.P6BP`yii _%%H {A`uk\F^Bu`ۢO '$^/9bƢLI^^/tI{QdN`:;%pA`k Z"!w~:XPxĘ!Ph>Q΍7F{`ʂ/ vh莱悄w|P")@YijX-+BuNjoHe$jc`|ε hf_>](B/ kSXNp/Pڜ-%iZ\zMMEz/on5*eLV65N @x`w[pջ@ n^R4E/jԞrTViZ*s=AQ|eAB d:z>J~f z-Q>>>?88| aNQȂ3;;erB30K*ōzcaa~A8 8$+X&WI?Eu( 2CVP8(5?A 3kkkP333PlHp)8SJ iii  U4G-(sxfO h 03_5%ܿ4+N7Yuܲc=3vcUp[߽ 1fg¼ēR,fܿPÝtGd)'^|uwT"u!f, T\vsk 6^aSr1<8wk~7o6weXq&ɥ5'pc(ZWFk7s8n4itLr ]h{{{oo2Ep8kaA:4R<ә7;aw(<=fX@kA '2333k۞3fz dwwNC* (%cb 8G(92l6pvP$!8J^4# k8+cdH E]\\ܖU j٠8\Vid$ZZX^$YAbV YW,teBLAʹZ@x۳z\,hd=iXɸ p/fGa>cr,KK6L=b;o}F#Igetstln! 8j()]zf/g$) dgh^IR[δ&Y׎U׼ Dfv$3p:[zEu"]T*O߸ ;kmܢY2Irvp/^]&cBnxR.(d|HKh"N+&Inu~Ёy[=}J9N30G[d; ײtf$PUJ(D4Mړf<)w =(@1y 3@ %0|!!M;YZj,(͠C~T:99ڂϣ# iAv z]tkBᓙFa=88_ϑr5,=dmml"/|R1X$p* I-..±2r} #(m>==ų=<<2CEVP*CVOsT0BbU(C)jvaw44aw8J`VXTbACnpT(@ gtE ԪA=`8RT #&@xc/xWor9țqAiSgL*~Cח»} 1@`͘⦧K˜~o2 P/Ğ3뎵N2sL _8ZdaCTDJn[ooV_{8fs\q2sқ(.0vRɩߐ՘[nM#;Iug^ZBh !w-!x&J\.V cQIgggVw]tABG;>> 3H؎(333腴G???T̉ $C>FŃ" { i(^V^ e@W)($qPN ~2Î)lA>`z] O3vA |2 iǂ#qE/{ pGx'N jUm ,2t k E`g^$lqDx(w^ǑpScIW| 6D(e9mV_oŮ[-GHyQ#YtgNû|b$gR@y#UON _csawdI:f'm3iw@)S]!j$V9sW =nbBO׻gbyW$ &ٰݶzE ….ThIW2*CΝ{mvZCOѷ$3?ѾG.B9\ %L @yXF iA~s–nFB.r2f9;;B Zm 2,,KH333h4&<::jZH)3(LXTK~V%N>5:Ԑá2j)x+ >!%]dPFqkk NHk$3Y(!ʴ&IU[  2Haa~2Y c0''' _Pa ?B@ԧܪ**@8(Bw]a_(GA Hy}!bR%@ oĘ!<-E+G^_!.K;Lf. 3@]&Ze9w0I;D了w]e˱Z/EƒéNLŐ%5#%c-m6 ՙs45$ܯhkLjuN%T{1uM dA KL oH5ETQa@ !k;3.F_`5%kw=4\zƝkT-Div=de.^zh 2Z*!{< !W@ i@ΔE-8R.M .f ? vPd@ (J\A_,8:5!cIǝW24 Y5Y@q;2fPZ%ga'H SՂC@F1nĊE ޞG Ղ4r~~C-2 ϫ;CǨXdXg%A+t-'''XTT(L@ (Ҧw]&%ZT] ;va[$qgt7(])D(fa#MY!V,G|]AW}΍utID~8 D6QA#g?Ź k M %`؅/.̜j{bL +# @ >X ͢ İ܆i̒A 0S:FM(-u@x @ Ę!\X'eY,x$@h`gClXN2^<Lmc`h6.udy_0ɬkk{ @ J/A*( sUjnk$ u:%T\Fw'%"h4 zprrv dGQ7K'"rY Qcb$ Sh#yF-w)]*ӹTg'r R/*[@ }3܌/FE9ZUU|]l$V㸰.#cYiof]gL*v_ Y"wqTDC&X홑v_UENrԎ->R<)EJ\2HW}OIތ ɂ+  Y@pAZd\~ e%ck$8Ĭ ?:/0S0@ e%lM!yhZnZ]ZZhnn.z3Â4ZmffT*O)d̏TBlc]:3|EPΑ+åė &ẝ gC3@ p@C`2eNl&.c UbK5D #8dkukf&7#r]6l ]f)Re rىѺ𐐛1 e]cw*2o4s$ f.͘ ֹe~›iE!`G&+[gxU +{WS:@ ƛʰwizzz#LlV*ZPZ{vvvrr}ttTLS;ݧFɦ,-N_b@ @ cB]sT<˰.sfr.]y*;ة~A/R4 ح*uudQ&VOQshm7na`p͘ghNel.2I=zB^!7cᢜ?j2Vn'.y]Jy`@O?R|.RM0Hki FiA 16hct,/KKKzT*e(B HϏ[ۇn @ AC'4\Jf,8rH5ˈ)?ե,v97f';X; y#Bvbl+3 EqY]H6}MXV?:Nt!E2hN]0cfL)D175΅U[AAWl bM3W&yo1ei,s3R.( TqY{ͱ6mc%@ t\e6LyL$NlvvZJ(.====:::>>d# @ @  1Cx`8B >> ܔix{/n)[~dkϸ|$dc3X{mGwgM{XڽTo^/U'쐺LzM颺Lˤg2ܛ210cB㶍~ QceH%*,m #sٻ5ɦz^jx ôD MG=] Z)|fYv[VAAA $t: a## @ @ ]3|$N,aih[-%n?3.ïˇfLr4la4{ώ22M UEHQ]el̪!+}$Le)#8)OZ J )eJJoʛ9މ޼70wb{1]uJ+Uf*h-XJ3l"A$66\N b0. zlq̶{#R#r[ VècТa}iZE7N(^mC?cZ`LyvRLtBːbCx}/q\ mG?h>u⤥~!#OKUgVG}f `0Miʌz[} `0 `\s-YcqNWQ WjI#Ăf?]/ 9wsDwTlYQ\ tR-CRݚ IQ)^śQԈMS$T3V \ι hDVi-Ռ)p'Hd`0 ='#K㔎 ?̛a0 `0  33F0cq@,ew P3K0jg $==[&qb2A %#`)dL&8- tfZc]\.Q.d;*p?ꗸ`0z/U1e֓8i0P1WBΛDYnVX]ZmCBW5(CA ¼f 8ՋꨒH{CA/,p]=V(n(pb0 *uhI13 `0 9'Tr3f]Pμ8iDj-W^'ڛ]϶̛iOˤt}.I*1e\#Zf•EH3煬x3vgG+.-#EQ jN$T`\c% 2Cbؖvt.kuI#L,..xSMoU$j²3=U("|E*q&8('`0 պ#Jl[mǰgHf `0 ^0d2Q 3f ²Y2-JwN kQDZҙϹӞ[c-+KQϣ.̌!o; TDknO۞]w=\UaTk ƅǻE.rʼEJ;Z Qb5Qge` n[mSmSPB-w@q#*iÑ!ˆBju+`0'1p4cD&0 `0 JfXxLʌy2t\2".qQA v# 3I$TSδ$ Rt׳)u>5ipqʞwe^bmzo 33zsr۶;Ff- w .%ɢh5RF2;y<.1ja; yuTRhB:tjJqҌ`0 ՃBi;9`0 `0qVW2e18/ͤGI_?XXUp2aqα˚.E7)R Z ty7;e;Q@%;a w88}gH83;ɼ˷i?f q1̔գ,T`Lv/S;\teOeIOyhe &$u6 s5U׶_MDK)IӡhR$ `>c;v >-뢤֘a0 `0 ;>4/EK8Vme,f0K;I=9u2z( ]Ou6]9ٍP у!Lcdi*O5+f=۲VɕӾ;3QZNW 7S/ތ/ eJ Vc2b&\&RT]D4L767{=DTj{Zv^:-DshB[ `RJٶmݝ2f `0 nK/~qIIv9h$zeKSRf<9 &,+HY$1+rʞ𜮠KGYajQ$A!Cyv0ngDKq- .OHUh+ڊaz2ތ6.fiuNE"u>eKЊk\Xq30 ТL)4sL4$hem]u`0 5(,I3Q2f `0 xV<3cV[Ysm{;JZH%D}N Y1uTKp#:f+<7/IOL2[cj#Wa-ԙl`T_R`3V[aXDJ.`\-4e1ǖZiEk\|.3ٳ-@o:.oS](^󷯄rHٱ&jhTɀs[I#R1( `0ވͼ~ `0 `|l$a 5zm슅7D$N/u>9.ɓh&=ѥEd34]6$e)94BƐÎ:U& 3[bP%pըļD`\35*5o֓c ЃG+pcݜ7,Xyr1NzO2et0Pa1]^xǶсG\U_SoKtMx'zfB `i(33 `0 x$[e mBHshuvX v~ ƤWq}4Pet4˸5_J!&8IL@^@.rfQ|ڗ!z+v^Wg*H^g0.`0 #Ca_`0 `08ŦA9#0c1)s离 uP .1$5ۥX:V-kLRz7$Ĕ'M'' Z5rdɳ'g<9$5(8Q\[aW8`gt=3/(^kseL OLn9-JmQ=2A ^[;6|,U[c_-77`0oY~ `0 `0f0h(_NnX&n-wvtZNKڢ1WDW]&Tve<43IIt)/aG1ݏ0˵(A<#lj3fpB %WkuGoM mybe({O&AXuI=6 "DGE[?@Ą0cFt{Ɨs[tl:R5tbJ2s;geoy'at2ϥ.cb<٣Rxrn/:fJnQ#>U!"Yl{•Fxnqo.3Ŝ7 ߡ.D}:Aږeޤ+ˮ0Q'JG -zO>-%_ҊU|3¶\Γ̫$P.WEzwT}2`0 `0 `038*!]63|(FAőujD2t;"^V t솑loigKgs]TjVo1eN+aX% YqU%7M$5a,nٓ%&r;Q[%cBԺSi*Kp˿a @9So#ί$C?+}˖ ݨLfݺc_Fl$߸1 xd `0 xgyʌm . 9$e92V/u0'>@Oђ^ zaۙo*uvTp ,f/1%-NII,lMqyk`s3(RtcuL0& l] qcOL%K.8g8K+lQf\OG{ vl?hw `qg  `0 xٚk) 3fo 8lO]ƷힺLi7¨:"4t-j*U@]@ʮ(Ir0ލR)5ّ?$ĔNx2/*][Rĕ ڋXY6O1L#s'\eRuJT  .jt{5 Vwx՞qn76Dkˠқm&l^MD {`07)8I36O0 `0 EjL3f вrL2Ac٘ʛ); iw4]&Z`TbI:%CjXpSayB̀Ք'BP$ُj' t,^g0+#9+r eW]eOj'Ke*lr{J{.8o5G2)b:pL`!;#XqM`0 sMl$&G34#N `0 `0!r:]%5c[JYq3̘a5,yߝve^t2ʲF`f;:*gµ)|wʕ&)4HY(^ m#ؐX,=1!b(VĶO g©6MA"! ƛs=2ބ+` (Zv4]&mRgoĪB2qFvZ5T3km+'8?CVd$a2+`07'tb< `0 `0ޏrwl[j(,~[cf0^ uv4]Fo(V7\Q=;F6gCSRҶF5]ƛ݂!8"-TI/ML{+]ڒnֶ7D:#cI/crƕ P2q-уqЄI]wR'ݨjۖAǞu0+^o҉H][6/3#vMfOTgHo'x;5}1zsaىɦ Mq*yQ`0 vKz2N*0s ]`0 `0s\_f1x * ^moHގY:j'\D{_H͵1)rެ:8ja V;Q=ĖyB H(Č''\H0Z Hņs|sZt.lԣbiڊe$k3ʒ.{fi*_+& ﶘt8J4] 6cb Nprs"LtAX&TD);ۦ -#Ï`0 S!ʌnc0 `0 23}OM&̘a;5$I]2]6Av(kٸKϨ˘:.˝pctjmyePʽ0Oq$闣Kgpyv+[AT z 1|vĦ ϒOqMӊˆeLF]cꦦ4ƪKa7 iynq\P-l#3۷̌:ITIxK1 xЕѤa0 `0 D>k?Ûy+y` e+Yf#UrQI H"-زjaҎV 1%~E/d)/IWN{:ea"P (ZvxB Sreٓ&chρR;rqKq 6SECYQWMNKۊN36V%OZFfnQ2.lɱ{oEi!fE'K:4 VhL `0^CBS;`0 `0 Fz9FP1a U{S4oz8^EpvR@qʮd2%I-T+p`L/I[KأRru8=XmF5JΤ 8δ'gBcӞ[rG+j' R<`OE_c֨nm(e ܂ <L'ZjLa\E(N9J BlW;M.QCAb~ `0^? 6 `0 8uD,0cj@-mMbwˮzu( (yS=9s\){ Ax;cy5h g*8CKkQbբxQL1'F.ci60e4]Fqd%c tG5he=a{8=mݢtr_; ǝ8I762!K78$Ō`0o-zD^f0 `0:N q*xĶ2Nzo̘a2+ޤ'XZ`&J0^i lS<>S\yeW+%̛d+8 Kfl!qWްa23Ynf< 6P(cE*b q^mm'1i ٖF ƄG ̠cORiL)N|lq2ixե*ՌNr3BLNhA;t,GYPHuX `0 `0 `0 3f/ ZWlk72]0ђ0˝p=*Tި2:dR/yR/D%)f}QbVZf2`0 `0 =@b|re7`0^Iߡe0f0^PJsEqs$JȨaul5e,ɨ 9CBd1ÕKa6# v4$girrad^MT3V17R`0 `0 ;L:˜b0/`[aqHjXG] 0c2žvBN9r NfT:r;㰏Dg)gw .Ń8Z'_Co,E38eOxrXvFA.03XQBe(VQiF.`0ΛJ`FID]2+p5 4]Ff2Rř`L{YƄ8Kٶ>`F DjǍ8J `0 `0ҥ$28bFR̅6/̘aթf]p_xZ$ 3sx?1,P0&.rF]eQJ2cRYO#e$jfϱ)`\"l }mױ8rͅ*'I&=/`0 `0 #y\! +C1xW|q)]۱r+ 9eƤvw!0VpQl3*żNHT&k(ĜAqm{D6ҴXYXmv!U7ܙ)4p|9etDOcZhL_0&" C%L S$8Y#Kwj l2xћ{Xu1'"T ^J7`0 `0 ]EbJ1Y= xHmKjv.hA1x(M"f̔ztf6Ie^z {ʓ;e,T7tv4Q1rի&X)k?v#7 J7.Ō2ʰy*D} ")- .1iu]N[ f}wHviwTk:]P;5\'eLC4P%v{>ax"ӏ[] qݙ`0 `0 x2 u3u&bt[~i.Z]&.c7+f/9Ǒ`gs'cx;jQ$PRmK1)]FYF //ܤ49-.S`6Ndu6NٕHax Y ڏT_)&͔m2\qOx[_nf*eH}})iYcR,ta?xDًa pgm #zCSK:5#3vb`0 `0 e l ޔ2RdBk39(٤ ]F.))a\ ­ nӚK-11)rC߁JjaiQTk vjΗî2\8J'Q5r\(-S\ݲ'}Z]f31îT?fJil.ӳx=L-`Lw8n:6ڍ:6~NSb63 B2*I3 `0 `0 `03g@eBc)Da:r;܋Uԇ` d\{Ӯ dljh>JS崏t@%z*A%(҉Ht31WkT.W;aFHq,&¨˸; ' 2Ame`5 mns虩%끶x8Xl6EWQ"98n}`0 `0 cb0oy33_qi-Hx $aQNJFbړ&Z]ڏvo_"geWrX:; a*Ama! F0zHe 0HAqQgnv.BA;Qc&P>s `0 `0 `` T ـcC8̡YD{a7)e,iO{cS8(x~^2r(rHvCGjj+vu1ugڗ(oAF4Rafqތ'۱̛Hv•v2ep*r[tRi{LN a  :Q6\`o 1,`хƔIflv7Z's+=K*k˄QK)^tg0έޏcB1lgkO ``0 `0 `0c` $Vٕ 79XV$;a\huJI"'=IKՉ Aԍ2PF!K%1TBQy g7҅݋47`<'oOBt!)rV ЈT%HoY9NxS`LJmR;aO9.]m:J1MwNj,:rmTw[0%H&+`0 `0 x7 mŜ7ѝցNxւG0&eȢKvXm7aTt<&δ{ݻ`#V Bnceݤ):S^t hH鄉8qFLǶ)5ٯۚq etj'eo$NCHHO<$AG8.yOX廂ZVd{]-"iy./1ޅ?FBl 8::zf&*93qʄ8O^g0Χf1!#wn  pǿnûB]F`0 9K±/fd3fO] MBZ ^74ZH-M +'LL˴UQNÈ|$] gƓ.bTllEjKrm[cRhq!91j'"䖻lH6m~6 IKM;hKtj)k۲7]Snf ɴĔeՔUžxWxt{1EaXJ/hpFfL%HuΠ*4s۰8ڊzcJ`[ )5C"xƇHx3N.HNK`0 ERt^4` Sbw}9`2o$NP*_]u|w“ tT 6¸^]pI#1(+zJx30N(#N3-:&2D[a0l 2T4rvZ'h4f5EILUNI8âۇ)+i¶j=NjpɎ& 8~I%kVI۲4xgZHNt:3oeCsrn3!gу#%O~K=;t z@2˄Z#(`C8`0^aü? `0fƎܛqXE3/ f00K™ף˘D;a?]$C2=vw˄l +=wXJI DŽ[lG*HAḧ́otұփ c.8Krh|rDdm4۲öI3VU *faי tERAT fbYlҐqOaxSLb!}xo~#3ZVαxT A%\e|?L^t7dG 4zfJ <~B*b0ae'5F.""ƌKgXxzKmFa`0s8$EOheVt ;r/34hd|ǯ/E3/f0j+.. ]'nj7èG|#XVQ OI7DH%;Qle: ټ:Τ n f/\aKiP(AE;<<(bg`0 \xFFF Zh4r-耜aT2jCFŦ)ʇE4My7rŞf̼퍨'3ys[pjD+ʲو.3,9ߝDb!&NᯩW& 1`HoP3|t1"2ar:Q3eW UVH41eg:)2t}Ga٣ҙĴԶ{ex="V}Boue'-0;Q` =PEBG2PC^tr.eht]`u<;0pΏrg6ER xU)rё  Ł3#0 hZZmooht:]OƦ]=<<ذeڝ̻c `0Εj-,, .//rM5.ĸ`qq\.777'o4vOsss`^W*5DʿMI3o ̘y4wDޘtLR NAW0&9NWŲfNըmBvXX.[aNئS;5 Ҝ ܏H/3Sᘵ^4qetu R9ָt.cAXuJ}y Ԁ̸a4vj6¸t{pEı|' C&%8G򩅣|(I11|Ջ3t@[cD Tt`0ɨ$tVrD4>66Z,9bI콎8a;S:h==jUjF$3ÎV/1@'C: ÇRt%DZvRʿpA+jf̼|=ቹv6hnqG3==ȹ;,ˬwB$U MWmbCtq ѡ=9g<ɂnWƕ 0yF)XfZJ#^-:Nw^#(UeY}AǞqŜ'F4˄J'މ[{R9Ky'DĬJiqElm@pLPa0x0e6j ݠb xa{ᵐ>GFF yQ|Qa}?)W[ >"r9ˡ;;;((`0 |58ߦfggquݽj wl Wڋ9pI~rbbqQakk #xH_D+DyWLeŒvɕӞ;J|Nl]&>MbYt0&9{OeJ'FR;a&}LyFL™pedT L WW8Ѧay Uo|ͅz_)Oޱu娴eOPnatJjPt2nLuڈ ގTAxٙ=5踣s<";o2ӀA%ֹڻc20̘a0.Wԓ : -  r5Sseu](4(+?]`ef=VF]&X^c+ғ[Cб:wTries17 C?1xd`\쉌T Kړ|>?66677wʕQ)e٪6iSB`Dh V#BJ̫u1\NK63.̞۔uL(BQe> k`0 qܹSZ,{Ղ׊/|A8Z ǃ *왧21Y~NrG666\fNTI? Llg-y%fyC}1D{}:f̼X J'ezԓJmq;yv8hlkD vOevjucH9VYy/@"-c0ӳZʋSd\&ɮfD`0I!=cObޕcK$a^⽨/e{eF{tJ*tRX]~_=؞[(ߏܯw"-gk?4ܾsXgB n4`0^̒S獌^tiqqT*9S׫Fukk{oo0BQ\X,]m68' #JfwZy&'T8m qf2!k:jct-'`0 F֕ :44D3'|Q _[wp=~=~<~7}[6guj̏ܗƨUrӚĠZ i#x >t:t\pbNDdSf1 ˠYaGM' mb`#^ $/Ę̘yQa!rCbCYm j*eVɕS0񺡡ɫlALrғF_[q alMN9vCVE!i=$h~ L9&۔hLf/sr=O`LS.͟:PZC^˲/3XE39`0/cCh1MLLP<1qWVVVWWwww;HD=\A:ڿ$iqٗӯ/sǯzf: `0d LN,E_Z^ * G+ӛ}[?3v$!9*VksslM?1( Via<|"jaoO(8S(~IjO)bx0ĸFݶ2'~.3fCVY4]fF'ُN@'mIg<׉VEjC+@ġJڙ /;6-od;AtMʼRLkfpLzW:F!~1sz\Ws^pq9ݕ Iv eC`Y}SR .]Fp3:jo.xnFG8khu -f[TGieB~a0=`0^4.###ӥRIJyppǏW*3 (;py.<-AV>;rM}fg:igM5h[d1s?Qlz buۜ$ #z'RƳO!t&#H&M4HEx])Z:-*H $|G'k'>S׃Hq;'3S33CBc2t4 }Ki. i[cR rPt-5̛J6ccjyǞ9ՔӘax#ø{S/w8͸+F.]'f#:l4آzbڕ faTxe4²3銒t|1YR$T1\F2M.RDݾ|X 1 8JoXJY,x>o[[[+++$#$~BOi~< oOYҀ&R"ĪN[R]KW8ٓd4M]ii襴BOw8 nGOs~b iyc=RS}e0 xXT(F .V><"婧tp9EU|LO'[ƴ穮PԻd)}zFaH!p(hhGB ށ/ Rpi'(8*S f̼7ݘ$ń+X/JkKJyf:%YielMa R;\/:`}!!&]9 yTe7ø΢xA83܂ђyE ܍"/2O=YOn7FlD6^3(scry5PcRMl- 4s2-~ *#It00-`0 EM;44T6(mmm MC%OI38ʄaJy|~`` ˹)cW, Is2F73q I 8dIۆǑ #)|QtS/ w:h4p3D<}^4M#sntWH YB:*~`0 \7<<\*FGGb7wC dfHol ]qyR8woB'):e#L\K~/E,(Q)SΔe{n3.4!=P(WH(惃X;"qqJ6e"5΁όOzN4c8ҔqGdW}O{^D*B #ʹ?.CEug?wuaVÃ@%B+Ə4ASH&w<ٛ2,f̼/}1p/:AJmZ'{uߝ܁DXazut#f3*-4ޤFDqN/5+(^[A(6 92S930ґ Wgcs=W 1<0މJ80 =on4pp@MΊգ qIY4OA5P(fffM#c ӎa%H1zGSM .DѶhjʋ[,I2Ꮄk?|C{gzv!$]VkB\'HU-j!H4Z 9Lsx`0 #0ȡ7KiTBS [l`lF CCd[TgI~Ӌk\P)E"4含[OPĘEGmkG3WU$d %2077t+IW !G9ωғO\5m@#҉ ќ:( $n:i%eD}gg)г _mjN$ $'I I!è+d\hJGN3 Kpڑ[! dOA=E4s1?zhuu`6W83KWbΝpeαK`ƕ  c{rٴ[CGjEeUeg[#w.)ao.D'ߎ9_Ny2# ]FULc0Αr-$5ͥ,g? ƫAT.C'f.o~$+3љ^B[?榧r6+>O f6 4!N) Pj$O<88H{+ Iv^6>^auV{S猌P?q9H3b=Qh̓Su1vvN($SZ:C*µTo)`0 CpHDh7H %HOf8GYߏR$H+<)uR@RΠx"exz333BL#8)|])5yTXJɡ 4RAcI\m$/$OR4Z,EV/o텢5%&zْj%"O)O!eK-Bm*B+ Rx) ${ :xĤOYA4@(=AQ7C=܎$yi;zp'N0miE)}Kצ"677߿ZFLfɜlָ y=2ޤ+rFDQ j@'g^=s;iw*E'Wmb J%PoK8YJ'抲'RP:* #\[YQ<@4 )V](ޏTűlG ]&\3td0 `DcPfcS#@K2 4!)r `0qbt:NW+q)e>t RKhDhh)T,&K #_!E=$K[ci3.}-&s) A`DϕR~b".32tp/ܔh.D,2O{ji!'բGROafiC' `0VB4fʏ!xe8\Dr#I=f%FDٮ\D,rR""ӗ/_^\\C zxxDHO7I \Mjbbr8N rIwPb*P#AQYM Dh'eV_Ԑl8BN/jMCw@k[aa}|)2= XUx'ea :ca&;`0L #Ǖ{T&P@ `0'1K] DHWk&Riv.,,GQjhebdddzzznnvJǀdiʕIe()zZ"MKJnk)Nk4 P҃@Vo?@LvhO$-=f)H½=kkk IFB)˴kj7/[b0 xih 3Lp/m E_QW M(iH>x/ nnn;DLqJ2ı&9\AHšBㅿjU i78B+佧QMIغ%gmm G0@)ZBV*  ڦ[RMJ~u6U5R3Q^"XXX3c9!gzh@|tGDx䄣>QtPo2NC!Pb} ՛Il3!).sQaVw% bTe:z6bL%.i̸'=`7[A6ørnϤ'{qUMvNme3eW&nNj;᜚1b~Go,Cs)KKzt m=:(CY".2/~'ބis0|ca8EKgl6n哃,=(Ϛ*iQd\4;湮zwm1ai`8%nB#3Hc?(JqZ:WKm#eLo 2v!]LEe5·bYthd}i q= T%4jk?OW"uw"T*MZqpբPG$BAh0)Fzp`hHiT*R=lmm8dҥ@&is~ek)FcwwZ\e088"0qt,#'&&HZ}x1R9<Ґ1@qwڈie0 ނ)18`bN9lp ጑( 1fϝ8ZpH.2BJ~)c|יR<7Kp2)#E}d'''q-|?G^4 ɽC"iDq⒕eQ[} W$\,12G~PF٨pi4m3=zJ$!D8wpS FXVri fTf ~M\!D1q/b-DtPWD"͒!ψhCHI$**B"3u7Kkh1D4l{ A $?&`>ӡGC- 0c*Ѡp]9:9& ]&:.C' 8δ'r^u|ӞeIUè^ʚg%W#ҡxL^Va۩rN<esG:pC)t`8Wbҕ6$Jˬ?{819B:ӞؽMT8 I##piֹmx hvaj412,t9 ?\J% K,C`0 A .\&|>Q\NZ8op(ǤIYujj g‹WǏ=KW-038hS*3C& gk*.SJтtO#n ה@k][[íWWWq/PuEԄgÌ&F+C*"DzO'QuO6I!Q #Zi^CՋpO Er>"MiH3x?iP6IRHb#ƋG[8S?`̻uBxrwBmc ]F%ϊ@F+8S.㎛`LDj+;a5*I^iǶ蔥qڱ >.cr4Y3Z8J; 17`|ǞbcR^ UVLHg%8=]j0KB{O>te8$Be~-/ހA%2,}'.G>=Alܿ? |!>cqO?Gjqq^|߻{B* g% J(ٻu`fZ7<72:gM̬tGW\kkz~ou?:6%)! thdʌzAO?p]76*s݉>͛š21Rk?ý{Uybׯ_ ,/-}ccc7oF dOˏ/'F&''o޺uڵÇ4(`{,Iҝ/C3a n( 18GCCC8V>}_w@&$>Hʅi_hm$";?-Ў[܎lH>݀{D#ssĕGzÑ6RïTU"a$sH08 >qn6/*`0 ӃL8jᓼ;MIQEq⋧5(Axzp (1QșL=gB[rIHi$]KHFq-My%ڏJ\b֕\*ݐ#I6 )F(TiI!PSx}h'jM{kmDqRr=歠(T59K MNJ2Ly)R͓:&衤d$>zh}}R f̼G3\1WSfTh#eި(IO\᛭.4]fle^ZT8ӾcSb(î1!ã[pE/F 3}8 T4L1)z0a4f[ۭ58Թg&jgPAFF,'0VWX,0~0GzBN\.'] _݌ ]q&9;'3Y FeB=T cu}k7MK>v/]~?%V[e0;i= x4O g8(Hl6b0UjI-$kO,D@l[JCD5{hr2i'L]BN%mt&!uD;_IS~(cC7yg Hv" z~o `S@DX<\.&ݥB qUT5MiH&GRה"iJ3MoAu&V {^Nג .7 1TJaJObZ/q+dJ*8D[݉hYuR!9e4ofksb-LY`gf`w ;ym#18?O> G <)}wL8  QCH(}zR"#Kw SBl%;3~ܗN72IKD4ty#=za凒eh*QdYyaPM)وj FZc@ Sيd-$OB5){U`09{$-|>?44 'EOM_4 IcB!ա!L5H^Yj4)^I*) Ƒk#}ZEH\4Rz-{I%ŐIz*T I䂦NSg5AOy[1u³ӄ~Y'<5}68ŴM8 ^Bi㙦9S7^` Oa?=/x!jڣG.CrSu~wef.>N2hqd\<3)aֳ|9aYjĚFW(㮘a)znON3^ Ӟ;әhB=3F8'(iWLB+C#"D[<.C޵̸:^uL҉+j&ꕷy8}W׭ iUM]@=.3Nik[|dfӤg %}G#qL˕p?Q'K~.xi_GESébG&rV0wR)1w0(gWNDJ ^re~qct56:Z(qa8TW l g3OU}(]j}x;\"U`D3jǚ4sAJVNZaTש։By%tpm)`Nll.//Oꑌu tBasso_`LJ% f~0<3f25/6ƓL"m>+Dq_23,Vܳ摜d$)GctKz/8bػ(p`04f!2ms\N*UMxzJȄ)M?IҭXΞg{l7юN٘a!;hM:P"Pǚ"Nk [PEiIoD`0 fh.P( }:'*ONNkQ"}Iwz\'IN] A!%huҜ M,z񦗧#tZ*UN;5uD&9CwK6۩΢Ȩ!]h89SPJB:xrem6p ǣǑ퍍ڣ!^)ًZ 赯jxhVWWqLKX0c]̧˸ ]&VN8.c]FG9q}oޢūh5kH6)}wܓ ۱ j#-s .)b“`3'A=5S`0Kwq)&]Q$)lmHQjgWJ̀IgT:9r.3ĕ0nVog4QȬϑ~'uq8SƩ[ONN)tG񌑿N;<<<18cWMn0t}'pm8F5Չ [`U鞀qBnjbH84D>1͚Gӟݕۍ`zeejVq^'fR[GWܤ:vv  WwՃg -[%_$iWzRlmS3V*hʜ tS=;)#S X*|nڝc,!Z`9I%vݥ;cSKO:400v-Vb1Kr pvwwp~Vc^r .SM/()jI,(>wq4`Ldƴ wtissҬ%&j'Zi8^y#7t)ONrЄcҁ!Th7Tu7{:pt,ci*t)Yp&et$F7U_4m1{SR:]*JRI툳?19yʕ|.G >J),,,\rT*!Ǐ5[Yp%ܿh8jj~.2ܠDڏd#dp_?P, ) AVFGî9XV>xp]xkfgsܬJC|;yPFtrrn6KKK(ڵk_ׯ_Ӗו~ AN3tG;i8QA"SO1:2LbTq} k45ijjb<1T676pUdx7nLNMmoo#);Q"߶ G|DM 1EDp0FisD9s+WL`Wap!#{jv5\6Nq|O`T|0ӳW0\ssXӾkZ^._F)8x2G˗/_Fa j5g꫏)W`rѱ1/ln>OZ'fױ, mϮv̠hc>zT][kSW^E>{ a9o\+R!$Eڭ֝~ǓE_OMO뾠\[YC ѹ+z7@NJ`0gI Gӎ[tpM+$RHAA@sdp|DU^9o=×!d%'[s'r_Hl]x_~Q `**)yn;22q0 cD'' ..|-\X4`2Tm+#Lxr=z#3<I9'G~z+qJH?k/3GJ f!~ŸVmllTխaU^THnю\MuR}&<1s+ d[ZEe\Ǧ5([=O'}w>A۪jH]~Mˮ(Hb+Ir`1mq'Qgܑ"Lyr~W_ F <"V8ʵ1L{ ]ޛIhaHiH2Ӯ)B[%Q&5.Kk׮} \Now~G>O><1Afil676._zuvR(YpuLdli_r&,+GGGʐYAkZubb⣏?|2~RA駟ܼܿŗ_.^2(+šᑑF>2f`)޺y?-HC,:ݝZ Vk{k YB6 Kpus) !7nܹsgeuՋ-'Փ2?0>?y?ZSf8/_~l@w hzy(KUܨJ ؐ6hNm噝OݸA7oބM~!+WUY\$?~K>3Z/oJQ/_OL=2یTc:ۋ sQrl$ 9ߟq[pl15;33ow~ Hׅ/>AwE;V$bİViJ(^.-c)lj% =] Z%߽wwkccocBGS׮}痮\d<do޽`\_V*XWb1T8KKKHmnn/@`T*heٯʕ+(;jEFbrbIJ* I/q6x@}% ;<=Xat|;;;mN[NOtBh*q}V]ΡN$H-RFe*lSӽTH,̖=M<=Re `0|‡|\.gu|||llv vss3iOi9@\.gff}7iMJp{nG{G>P kӰtN 8\Q.:><$׀. JPV pH]&S@jDE801s]fLj˴'(#Q1˘K|.rƗ#d!lIrTto*RJ^]⡡W <ڠކw_#~OZ/ڵO?`w4ggg?٥Ǐ'-l\vmQ\#Mx;Clow:#hz:=U1 o8㓻?bX[믧gf}ll'~pRmw knp}}{/㜁rRj{bnݾ_oPBayÆA<y򫯾˅ǏݻwQ\>r9V=Aѣ?}<n߾yVB 6^官urwDxX7yTltN/ޢ2+p8yW_Ҥێ "l߈O a>әD«;m|,b͛>dZ1~ }۷aO~zM/g qaOPkxivvqґ݆qQTӄv{eyٕ 7}g|!~{`L`d`I>$?L\;;7wUXN |v5GҒg; /&, |tH/aD0~'sryQ޿ B5X1'?QþԀ1GuiN7fٻ> "4=z$rz}1 ,j[W>i.kC[8x9pyիWE&ͦ4{ o޺yʕ+%FT _)q9955^.cdbyi)1 :p<>*h|Ǹ˱[o?|}Z!gQ~µ/31VֲC#a?⋙5$Ӓ72FhI"LpC#M%/ ƛ6+vڍfLO4s4 6N s~m)r(;Bl ..,ȈZzOˍ`~'mtXF㧟~'2#RudLU>D47drraqqoG߹kIjdd䏻wqS\MLṊ[x$ q [Ob!͛BdU%$BIqBp@a)\jFdt3g`P4)SjJgN#;sf0 `0ZWi:c|l44z17 #T*mDo%m4hކ|a~J3Ym>b qdB"Y_.%ӵ\[({.`B'9Dj'dfIIV$DubdcT|O "NP02wvvNקOiXf2^`tD>3=Εb`mMɹzg2AdTap߬>sZGK2b1NxwB/4#F{QԂq1WLx(峣)/.Fԛ*qI'4-lkIJ60o{ՑRLeVp#:XrRyrMGkiuh%v#~/VJ׭jzNg0a0~ƌ 6kǏ){8 𘙝-[++f>-cnR{IVݝY 0c3!fW!á]|zfMuK`Y8}xxN-{ws㹘w0\njrrvvvǬ Ԟ?aÃz._blьv n#ǏƐid x-킰^=džF>xQ +oy4Z%I`;&hq`,)Fε]ؠ,> 195uᡑ0}ћ>$ SyMVXx'&'ZR ?mfF l]mo'}Fm&;Zdf Sӹ|9qC <r#377744{q4k:֒&RSdLToeVW 1Fx|^d .tdTEћÆ?޽{J7ŕ85KGFqdv;w`*Q@p5ݻ #m.Gq&&'ћhfߟGm.j% \-s;b0gJSޚ>h! XQI<' |o[\3Io0O5e`q.B"Z@H< d* =Ș]tFKL#*`}h'')Kc\Hk'Yv>3 x@ %"eX)I>2#J m{{{jj k  Q^ȗ+JD Ghn)tQ:MɯjrカUHpB#c*1i'' ^Vlw17PH& ]D+=Y6$ ƉBrpNSE":*GhqȶXz[ܦ^S#}[w ̘m` ™$,Y^{v>3SJYyS0hy-+A>əE)_..1Umx '2A%Q|_]e6D4 [*K{J™rEN|jUY4܈']1*wMMSk?㡉Ush pvvGFn߾=<2Ckk>䓩ɥG5671x7ggf/]Z^ZBR33x s|#>y;?4 .WcO@Tf 9FHTϔ6lo`rvqȽ svG犑yHi Pp͍?~}rr͛_}G} ;~|x!z\+rW^z~s$޽ƨd;QMLz/.Z2(΀4 Z+Qb$dt /5+WLMO7 %S޿wo\}Dquu׮Ii||aaafff)ZY^n4'7[nͿMD`D镎nyf#Ȉ0l~lO mYU&2R mtOȤIw_@矣~~Ç;;)f6 c2>۷nݼyskk iP꺑EޥBaȖ|Zc./ " zwÎ-[MQcz7 D3-L!T(xh*J?8~E\q9)t `0Ot)%1Qx"p#ӊ8 ۃ511\Clf78f8)Ӭ$B8i. H-> đ π".8FJQtԂ2 RobP EOna)_w9R!v"K|zX,TǓN7`gOU4Pv|[ߛ~1C{\/u{ׯoPsk/f\̎ 8zxʓEq6Z'\ikeЖ`KtZA، 7"Ĭ] vbV5L43LzrGB9+QX,0`c%u5 ɖRaݖ,s'lkL8s4mx.cDވ3jUՇB#_ed'2)vVk4sp|Q&vccWѸv|ʉ7n/}G*#208X$ˉ^S8hW{kGЀ'@pSV6|Glo:(ɭ[׮_ϑ` 7o܀<=oJ)OS139GaeY⪣ՕRe 9 Qtt&=JY#_Ya~R1Ƈ}4?#5D][&4솉!];)iqmmmkc 676tyzWpmÑm[4Y v-:Du+'*oY6nrir2}g{wۻ|_:S'&._7o+ÈYY_t2l&xV[]]y!L0?-c}tԤ=O :N٬T*VlTj NAiZDf@-/L`UW]/pGt /ݍy+ J5[ML,'i OA#BP"-H6]4A2}R i)p}m*'Go>ݨ1)Tdf*ftyhF3`IDGB2i#M#ഇ˳ۅ]ɉ,i'&EQCcƌAb"\ݭVsĥ3iUw/p-CEu@t"T:)5OCC11 `'H40Ip3Q3 DؚĜ9lvW*Q 455qzn815 677 ? [ 38BL3M!xIt$DI*dS!ҹI9CHx>͞SEđm<^>lOBDS|R??[)߆2OlFXQœ">e~0@l|`0cR/{ҕ 7 e:*V-#)aC:;RK Tݬg;^Dwf)/*cbwKRkjǪAO> cMM_'y(yw6Cnz'nOyz5kkH* .Q3L7cX295 7760"(Gj/e 7xW_ݺuK`Z IZh?5 FɪHՌAچ /-Ǵabx?e؁c{}70kF=>` Vo%onn⫯JfӲ[oX~pu*Kss8팡a\nP ӑu9Ri Em$H p[EP_XXX@::7Tzy`aNz(Ҡ \F'!2FxL cu3 K +D> j1Z 4:AstdVfф N5j #)h.Yc|o49tD=30$J3V*!n [6R@J4*ۣÓjJԓt8Mp& GN#v%ˎ^* } * qHX%yxzUGIg'iuudje0 ~$IbHZz"Ywơ=2@^Q=(&_ Q$"s&"8qOLAp>NsR]9Ii_HNᤂCts"vH21{p )i"Sn QݠՏۜN4i_nGdbQ@(8‰'``_Y~psO  MX8S4H!b 4=6)0o ̘xt0&spM7 ⥶L!ž$)t=oYXuZkYjJIJA񰍭X3f6ǧRpa!f|e:,Jl6}CJ%0edtܐ>`d򗯿8auuݻ{{pQ_oCw~#yG[T*=>fͿբxg>.Saha賖ƈM|*6L^& 7>]R`aAP3 wu-D;[[?0qhzPmdQadclnmnomd`"`+z'Z=LիWaժWΠXIMd,4+#OL"m}mO>ꫯڭ### |`scceuugggrr/lw:8?SݝJQ.tz]`NQP&䖤bD/sksgsc3Bg}veÇK J_r勯UB/~:3zZvSl433ɧ|ǩ&T)\]/t 󃃢 :u+*pمG//-f1)fi_H}%mu *8 AXB()ͤSN,0O^1픥ݴ)(,IIYbhtAji%Er}nوˆZ44Q+l;-~<~ҺOHKZ;ՔM \7] \pW`0 }CJh Jpc&0>#j'N#oè}ڀ(䘑?F乑Jfp(3OBTUb-ÅKײ٘J|ks]p&G}444l5 p=|w °b0;7cjIPrAFF*{0!Ow, !K.qGFuܝݻwo||Ï?/g6#~;whwFY+̉P@<\Fݦ* |VZw'|+G677[?\T~;$qSVѳlmo?Gl,xWM{UaTi!A<' m6E{!~KѠ: )'F$E"+yCt\ ӽmZ6axBD|9Vo,%[TUZG2o~%OJI@ qM'N*h-ҡZZ,A)fs'LݐSE:"? '@|#|II<`0 Q  N|0|p5ˤDp-ѝ=σe=i"y8MMLLJ/) I n$)27H5y}DCGD>>>N6*M# XC:iTPLD9.[V*?" K8!S (|m oDT?BoN~%E8G<.N3nb"UMPH cr(TX\$*_xc`̅k[%i^( f7i>3 N;δ'r+doߊjkS̭'=wP^mV;A- 1gGFfܕE".F>B uCeD:3Zu}V+I64-Ӆ쏯97bhw6\5P.`̱^vM:WJr1)RPO._2=3xg{:Sӹs.̴VݻwfyiҌ4i3:cȰƵ34@?zA_c 9.}1a2oQ۷ocpR yXY^w..]Sm4uYCawkU\X.O\zeaa!6wD*o1vF Q/O?IT;*#T*):@tgALiQRDn;u18W/ Nsб'tH$Ғ^4 ӂ 0 :~vmggP_X688ѣ_gnvo;`0bFovfvqq굫au7.$Hz{Ιtف|~q1[# CtdcxڝC!靤V@1Jz5=lnnm"U*\z|剉V k4(?Ft]MM|P81mya; YqcR#[4:V;rΝX8F eqJJ,Fa%|w?fyyyu#$mYPEaiiMX*u<­q,3,<fgg߸j [F# {O18/B!2tNCN1 toveMgjդdn,a'")drt-)wGwv;vk?Su}}X nbw22Rt>|3GOi' c!nmb mmn?j5??'u۶Ud+###pkpGcPH0q>*y THF"3?㋣"aPt pG K{#7n[xdmQCtšcR'։!IvB+q&eLCˑ&P?<dF-JuhfxȒZ.|b?nׁEUb{, 㬰( GOiQBa>8J&EWp<:}/=n{h:"9rS|wղobVrhI:MX}V;~̹?>0R;|p B5*ozٞ2^9)-4\SOF1+vң=َ,C|0>3Ewe]fσ3|?gS 62䔂g7ↇ/EEOk3zA0n+kBE˔mb}9=ܐji10RZĒ {S.DT["32وmԏ9wuߞ.ZOr\u~{ʡ:Յ<գv=~◘{nn裏x w>il'#CU< vVc=ȅmܤiWuz'$j6>Qum,:!@1&E$aI0\O_p_8ޯSqbI¶N]])3cŋ7rT󤒜YTA万!Ǿ{bB~珒ބ=>θ=7;b||~8]{A=ގ:*  _Z)=׮ƽqwn.&Ǻm.Q=~37W~:Q'ḃ7^Cъ_+&ƕ%ʲ,sωj+5_J7OOOñ`ݝщAվ&)vl䪥ք*eDL,''eɮҏj'Ur(+sDT1-fG u3ɜ9+ :BwÀZVt8|ddiMe^pCdA.$e]bsg"[QX&|ygCܚ3xGc vڈY9 GG9ꜫ1v3y%H0ゆ?\n. kG*!؜ m]f3r]es,'S.[L [!lCea!i¬+IŰvP-{!IR%&rQ 5n'k:Ƣ\Ap(y8mO;K=m,e%_|{3, !j̞o|zmkl6>#y!3C^eMHq0v=zļ ذ    ȱD&0G^/11Sڲ—ř4MrSA߇wwwXJw|\5P1saT$%ӂ/' MPuA.Atș#eV*Hv Tn#jNU$sR u;Jod>IcZj&oI&S?1f3W;X6A.ƂIA>#x2uR^2tgf%ɧF>BsL]=bdB,1Rf,elM3ro7!u #    \ι<[v0zf999YVK2jRkm4{{{ O W T\I.̴+"!X k!R~dL:Kr\L=/y2sR%RAA%lz_ Bi'>At͞ςC{r%]l)4 fCv3,ޤ`shRvK'>! eQ)wa&/ ^YeΙ!hAAAA94bynW*4M$wR9+xõv'G$<HǙjb>& rU4Ue9Lrv+ዣ=HT`c/8A~Q9rQSƎiZg;ƶ1r    ,P.&T"6˲F&tcETrݫ+DԸB%TDFvuߜh|W.4|>ᓂq2ʓbzv* vb!1r>%6s31]|1,pZ(sMGxC5Fg9`gB%w}c3O J9]MIJ Y*lCۜ8gA~Ґ82G1-.w^&AAAAAnA^bCh&F!!HQL°env@ LKv$WQ 4ei((SO26֞[.3+r"&U|^Qt^U9K1cm+VIPTL .b.'$jOip<\>A߬3|FpCwnWmz[:hʪHN/ħTfk㰳#K)Ӑ1v:t)ej"kbkG1qu eȜwSҰmɞ6:e^b&at>!6*eΈ9AWHQF?\> : [0Ӊ¸b|x#Դv-tszQҴܹu xwV=.lC3      /-BקI܀ZbJP!ZJr"'0(K` ԓ\rY)eErdOiվZ.rRt2d6 O׽\FK!xE4M|!A.jEqۓ׻lfW3cA)]H`s^ kY2mΎ ?j#*ӚР#8z$Xnmcs'2AAAA5$J`9t!? Yu]P1sn]P.yvXe;כje|?9r2Dɮ2j3pG.5Ζ9/Q1d Pa R*j#ِv@\78 WFz,S%t=]=m;MG-Ō%e2J''nC乺>Qxi `]0 |&e;PYɗ9EDⶍVor%A2#:ʹcwϦDNͼ1YS6L;͖M. /)b9Au9Aˡ}znGԣF% iAAAA㙘NF /`[HPݤ8CLl,Q$ŝ4OxʆUr^=erwv$r)Vc<e\b%ǚAtk|5.fx᫩"̑[uXCq1l|5&[lQNtLm; '2K>=s{^WgFiy)3% hȆLZU ,IqF     cal\!VA0dž2T\j"D |gQ(GM3[0gs$)>W,)Ӵ˩IYikP94rZNĂ"Ӟ6mI\R%nIDk`vr{: _MRAOoͶ}Xf)Q,M'sUA^Ls`}AAAA93Œ4 ȹJP KÅ&R)%RB2{J?F{APeĭTLaX>0@]ۯ^.;lK^De)'nN$y)? V5v(>A \v*atIJRb͖t&l;'bh%&t=msBz)3׼d2e(zhݏk`A.Ί:GnD"^AAAA^Cdzcșb5T r~b^qP1sy*c R=ΔĘ A.#|F<5ǁ;Je+|LYNa7á7>bNٖB>^!Fs=Atؐ=mZ%)&ҶvWۦ ? FfO &Fݿkݖ6[KWyh}9Q^K2p!!\e}G []n1EyyY0ъ^ AAAAgsHs(m ƘAyI!+L xH%0J"H\o*5gDHv:6l}s0S;JνvP:'r">1Jy˩&8)P~F#Ya[ɤQѶ :by)ft1(NĴ`) ]`󰣿\ZZJtϓec$\)s`xqЧKlx&?J9ـ㹲:===;NR> $pϤ*AAAA¡I;<17 *f.Rj*nrr$v'7>OjآO2p]! Ԧ٫/CbL/yrZ $ge3=N֬pDFc151(A+CYgP.hGVm[h%j%_4 h fk/,{ML|_Oot:~U+fลRiaa>9i.>n][R" eCGj7\ϺEA^ԐA y?/wn~_lۯ9wzzz~aʰupp` A:c8  \a!/EX?Ӱq4f Gj*q,D۵L=T3d8OJ^YXH.V3j3$̇/f#W uőo+9!YB eymA:g>\s<sPl!ܛo=;;Ӄ;;;ot 1@UH%mG6$sMщ0^[$ y1l0K˿?IVXc5nɑlJZSL([i zyewmF AAAAoAr8g[OƴHD): 5^.xϱ7G|%ͣ +#0HQ'j˰]eNaхrZr^G lA$vX3B_X`mh԰XeJ%_b\TkQ2ƔJo>Z^YֶZ^_/ns7#ƥ-q6R7y֕jueuuaaRVGċu1^Ghj$RN µͬkwcn.27+pr0??`N;Nε>ɍq};d]n܊? l&|~ahZJUuGcъ"\qmFAAAy|IOc梮tPۥȿJ1v=ӏj_,Kl&)VR$ENXGnxX^ؗ|L-~wYZ\,J~᧟~۾x֭)m ,GCAJC]AAA%?BY$A̅\'(IR1#Cڭ\=Ծֹ;{ʅ)oV"k"e6O2龻d<Y>Toܶқ2aRet^ 8!e6KYw̎mm,QB+c2cSOXÅG6U ˰yɧ92d_e:HL†ܥ>>7)c+Z,1Zosqqn駟;;SS|qiT.î?쳯Jp[o6E=99 G/pN$ nmbʆL;׃+YbG6>?5PWyIe`ꓓwg}& L%)5σj;;;;w'|r~_g^[88o?䓽>&v{KTJ2QI!lq>_"ƞ܄#v;Xr W10 -   <ƥ32G !&rF1̭Sf#䖜gPˤrR0q¥퓐ש}ŃqۧJJ*_Eʺ]32'Ɯۖ]LTLqNʬe>NrUnT~l0=\_F*IJ~CUʑݐym_(RI8W|۷10|4PJӝjw៰?T.?zl.\Nh4ffgFT:88h׹RoV;;=|V&7_l4`.$"N mN.+əXq[;V;vayjҨʕʛow;9) eg\ZY=Y[ﷶvalH>"§OovRYZ^s.V[\Z믾www+“Ǐ'OjSSSK++ڷ|)ʉ_)!+SiɦAkkBha3M)5B~ $w׿ w߁IIn)CY"I[Y]}:طoV1 `'n߹l4?׿~~%}of+3771lhk٧*xum8ܓG (q\VZKWAbL$FAAAAAƞ!ZƘYJ*#r6Y҈]dzPSFT+G1%x!\}kf$OC|h=rjcp8\0gF s% fϒ<6dZEFrXgݎ6^.cԾTlmV{w~A駟~嗜1!%fnRG3R\.7}n$ۻU.,,~Bݎcgf`[)w}yzht. eU7xcZomn6F:ҲDc_k : Ky$󸲲'&}?ySkiTUw=|l7>~'ORONNNMMU*X<0ua { kXKKKVÇ&D,`8 ro1WK:r}cE#   Se'!y9|JJg,''4 oIBt/s1gf[ JDL?n :'u/Bk YHQC>L5b1N~Sbl>Ԣ8iGiz7BC`& h˘m0b7Ad31VfSN%I}z͍_D_rqq J$&R†^G>ldx}_+2 ö`u~B$M;Ǐ~ NM1Byep՟~z?O_ս{-yH k/aA_> >wnνP?9'~wӟ4|뭻w_V ,[1 --oom=ǝmf42ǏFhsTE Vlw/TW8 #.jvP|w|:G[A%3$mx[rF   gmJfX37Ng\Ƈu 8Di 9utg$!Ssr4nMliqZat)iQ̴bISm(ڝXFf%_IҐtrr1U WAgӂW}4w@=-4XE$/B=bai4333=3SLҴ:1a. mÇ)JiZ'Oc`DVV~6;M:vߋbX\|*Γ$ͷ67a$L-}¶~? *߿G|~2I>䓸!"8afx;lb%u\3'oJ1p 籢s0ss:۷}fYIׅbXTmommllDY!I!D;$wؐsժXc`~]0`-r:#;lȔno_o70]_[+  WE 1}pIt,AAA׉"e1Wb\#etF9)jGe\&cagA%(EyI[Pf3W{Ja0zA5|9B ob+Rz`}c bCM뜯$b^4hml[F:"RFP_K[]miof]6nR |L.fhC/8Pj^_[w|\Jt?޻FQrieS׃ 'VnݚaF\O0FRՖg;Nf$c`3 8.,ͻ΃r=?lýʽ b$kS!tRS>M`1BCQf?IԆ3 )dMݽ/dG#JߛVFuqyyaiIF\#HGf斗;;;n߼ٌvwX>hyuufv-rE! W*F s.h;DGO4xAAAӜn0 bT̼*q"Y猇)F=e|8(vR 4Y2$ }SQ{AAHCAf>}zJac.'|L姵6s8xA!0\5f$q: E\Gӣ%6I(lQɰB 1vKevV|ccc_& cܖJ_4]\@|e{=MVo^Z^ںd޽ iNs0lMIJ!MVkgwn_ n1$1r[G8Qgo aoPgafA 3Hh4Nx>< {%~>{q+FZVkX啕sss>ofqwqa`ǿ/~ 0?=x0an 1dv!doE[ dYk޹{W)' ?6uiQ+3{BAAAd<2Ęf*3/z&R$,? $3^\2QDrbFR2#\|Fhú<\uE@ې2—$̗rK$JbS|Lmcv ?JAcLQ!ka$nQdX͖WgdY6 vTZs.|zfSO=-$J\w7|71Y_\\}?F133_6ͷ~zp`vv:oH!ڝjjjscÇ镒ШnEHھuu:,#Vt:`WqZ`Za^3! ]TZ^^YXxLܝ;|?/n66ϲÇ` )a Mk%p /{՗_nQ`'S33`aj}p5e(8GV^(0 !}bAAAf??|bt/xyEe^ 釨6vJ?D"1)HeA[\f-.?ȼ+,c%l]e;m3tNv!p;C;J-@+vߊq8J1K[ۆ`͢Ysc*(l`sp/Ѱ|>qT;#Cil6[f>`J%ئ_V^t__|4}ܽ n~/0 ylmnr2T [j驩啕5/QRHK^|W8k*oB)tI9Jtp.wzE,vM3њzlw]1ׁP0`Ǵ1 J 㯾L\ٲZ߹s;?|_}yjzj FaC<<'0!wP(4qpw}ͷ$hBAfPa   `R0\GcT̼;,Iq$(s/%)>ZXLd(B2(ȅJ I'ؒ0@f=BA-%bNRNQz#W-m-#ջ[A_-sZ, :y}eZh#VR:٬Ub)/vC]esrε?S'ݍ\/)+ ,_{/,ilJ)ṃ2 vnw4^W |V&Za,a; 0 2SKSp&+q I2I&ß<%1͝N +ͼ^ٜ+` 9ѶciNNM+ï}Xjjk;; !3 qE'AAAA.T̼4ID&Y^.yS0[& y.R. 2,b6h ovn6sL+1 TdF9)*av`f-{jl7rpAVyJ`ɜkӵt,Ü` LDMmv8t0Ƭ{{1Ry61$>ƌs{~a.Di4JFsru4Y?Tw[nѣG>rFkB\aG ZXD9wH׸9 A^ v{~S -bt;~W`@a;N[[[+L"l6ۭ|F5j_׃fՍ&.j AПAAAA^OMT̼l$r.%Fr@'unJ^.#rFrI{HE x Nf*R>ָs95> ;akj]386zY]3dk, VB>yQ؇e!y]’M( ȵ(>%!   3/, r1eHXSmzG遳C;2c+J^YGr^R XbQCJeжfD.p:Ƭjk- WΠ9R2`ˬ=+sm3܇*UL8pp*hh6vwvRBCZ3wrR6QsOhu\1c_pP,HWsks2״P5#- p0~ssht]{`Eh[ɱ?<Ë     *@e(V*o(ɣtf0q2+[JeO}63h﨧/qrrT)(bO[(F֞RNHY)f%]\NpN.6q?{ uFL>0iΖ}>&GqZƮAOWfZ Jih6p/ZnOp2(2gK^ o;ig{lTΉÄ?;q}g^wAAAAAbgaCT:Rq;1 zmIg%9sP:a.ᰟ9ɋ}c7.L_HS^$Q6 N`A.ʕDGz=Sja'(M)-h͆H[YY SѱEfK:;펶]VD'OHH/\ul_1&}$xCQ;hnqF 3EPƴֻ;;{(AAAAA*f^I.|t\a2y{Jg&)9#EBiߒ;Z| DRJ$_JT ͖ښ*ǭ$3/'b:JdonnbA+k֠V|a;YkK5#ٜ4`i/su>pzop9K!F`K{q F}&NVU&>VA <AAAj]鰨yq,!eJ( yr0SJ+wv2V*H}v264IjsciJz_uj BN6INk XgN NG+ $a h۱N a lăkP@[u ?y!ޛ܅CAjBJ&CA?^qXQn$gLn hEA} H%    7 2T̼uM|^:TQsefOƴJ(u mfr57:-'b.D˻Ʈz-WP q ż%6|30n+W3uv!U&;!%S|8 wUetI9G: vG|L"e0x.9 K$&I<.Sb\ 녙AZCa'-6|c(DAAAn0(c.[ 6翢SR:+4OxPbkEܝ1p~'MVR1!oKsmc3$Smm,9{W/j5N g ԫvcaXN;Zj7Бeg眝Xˁ}նmOچ ȫ’2O2~W gL,ex`V&AAC5 ^-<`ep3    7bhP%W I*fr):#\IEǙ~zdH*1txr(81v-S20F_`t˩Zn^n}>&@LKY!H.v#l?rMt*b.g׈"Bfvm8RA.r q34}VQ&E3# jb?<cAAAgRQ.\`*frP:#D 9@ec#TflQʡ\&li>ЏrƄ SHJf_NFnj3WP11B>zмm+ BavyьD2 ^go{+7nja.AƼ: W̌0v2p_n۳VEgM04yQ&Aw,E;tx9v".9(!AAAA^GFh"u1Ό̠b9p|M(5bMȁ֏j-m/r e~Wsߏ=k728Wn.ILTssAaC8f.ŁvА%C+&Au%!X5ٜ9VAӞ\qb8qshHAAAI90F\ *fK>UVRe.Kjl.2 oFh.TYTf[ل]l*-J4V5ld ֋XRDOD] Ga/Qz_ J#uE;|4vl0Vg /3?{6mYμj8!ũ--?x«dں܇}r<1\ᦌ,&9qmkZ C)&+@6Xs_3ҨE^r=Np:Y$J9ҵ]reA>NcO9I.    c3`l8*yISJP1s.B|Rat)Kţcr+SJwKɒ%>|_Uf-Ja%/eF%/d(i *mK܌f\N=c}.'\SB&?2$>'X1Rybk vϘvY0h4 ێ1aBi\3 1: \[Z54c?4{9 ' K<*#)·Cx ӜO3$wgYq FEuDU.i*g)3]lKt匶L( g%.;Z+͍- kCp>|t&C>)j#g`i{n;㟘y=Z    ȡggxLoIx#`+K!YNj*/92v}2s3ʥ("uy尫^} <3vaiX(̦/=/E\ TlnnfYV5[-k ^/Z YfgIe?[4{J[͍$IwV &ԇKFC1#o) G'4t3zIN0K*g:o }tcwP>s)nel*:9glK`LOOV mh$i JVV W~jٹ9Je~u:hshjU]$|Np]>d}nY/+f[MpxAhPAAAAT&  |wc9b C)لb\(F$9hx-+yKɍ+x?3_VVWlo6{E0T# 4Ih$8\ UCɫ/遏#:^%\:#.S7{梸u:J,4jSЏ>ZmKߏmi~aҷ~ mie䄶 W~B?7ԔRʌc,'Oj?ۿ}_ =O9o'|^4(nUXS bSbs"YQ$c{.   39ki3FGwKrN8uzQQs5ꂯb)1=Fx׮V"7ʺ5=<991cý~^~pWVV,j/[AwQ`0zq9mkvT*:+nJ)͏qM,%4g9!&2NL^wWf|+:rKx%KO0;zՉ N9ƭhKЖVo$ mi R Kb[u B\bv>0.,,,.-6۝Nḻ=C/h46j$9~†yS/K/)2֠-fs<(;_!3qjeAAA">Aw3WOI+s<[#R_lYDV8'F6s5EG'̿ ^J%AwczjS#Y|"fa>&r9m+=ކ ȉPڷnKi0Y)gXy (MX8}IGϾ"̌ʤT)p&c8ċ%C ?޻ ڿP `Qy`SZ0Oд=Z;3zj2!=em߸ܹ+|wQM"-xSFṱ& R0zE-P[Ѷ]w&Rh`Bc[O7ޖQJ%#jB["hEn?B}k~Qx0řO;Kə0>6ϽMcC:AA{~<[C A0qB2C/#<*fNaiD.HQs}ڬj+W}{dL1j"\۹u+$ t,wMЕTQlruۘ<1^NDJe]r6hiҰL ?YH+ i!&{؄MCOO懵εVJ#f7@nOi 4|+Ub?n_',!fa>zc*0ZYXsݔ.DpqskS9ʥ!PU` ڒi-=r' YiÈJ51LlwW)fEAAAA*f!eJ[,9fv9O28lSel5wR93rjWiuqPDsRLpq-T$q32)3e|+MH=eplA;}( i|0*`/$t#]2IQ7plybl֍Ҳ v)AWec9S=fc*Mx3_ J`f|ۄ*i hO>hKrL6O">+(PZ[1-qoE%   Uxj|T7?#tK{h&z/ceT.) LۘLœN̘(%dlt>)J"}\Ƶ]T*wB#)D@ B"mᴬe}3 FI(0V4e,̩T}b&s\m2*s$4uސ| 2!@Ri -?H!:BL@vCx6Q"f00٫[𶧥 \xn%&z 1)oK1m !7gg:dL:bm~hQ)xAG(!&AgAAA<JA9 TQ]f%K>^=wҎk8Sڜ|93r(ɘe(x&[NļQ$ܺݐ"m\$ʜ.Q*E1Fρ Wiuo) `RN2V^.Z4~3$rVVW_ܹ G~f4{n&&&>rԂF_΍R~ɓ'BNޏWʤ>-D/&^lsriៜs5hKxIf?V}`ј5fwo?yx &   <Ay^ƃܘ稘yJ RbtQ[eH6v-S^.24'D9 j{]BYlID\;2vG1sIlNQ5u 2A.1'ԧnLi }*$CQ鈽;ƕr#5M;e Sç}W.,,)(*f[ZR2 x>M<ԐiB}G!uoe:DJQfl3p{v aEPNK&5//"@;=wTyS8   \/A)T2DZ,<+*fF`~l#\\Ƨ e)r96 oh}mr{9⒒;ΡTS 1 YN.U4>J*0qh=pp8 A.ӂab\p86 o>92f=8b{Oz=( ȍs+AGY {9y>Osc4$BJ&NZbyOc{L"WSY*Gkڷ i_wb{l}*bliЄ"7~fYC#m^){z;n:]|Z`CAARBT tngŤ!d6rr!a"ut~N*ors"G<kxs3+E†C}cwr>T1OӤ$HGܹml0lFr$HBU82BD8z31?w>5Qys85R%3:J6}zC0ƌ{{_} knݠnR8@駟lml ~ʲ)BਘɆ޾siȰbe!br*cX]m #y}IقK&8yS?QxN}Ѷ=;xR[mwg'%Rӡ-An el0<~ښo՜<\)/9Dm]ڪ`ϥ:)N   \ ٹn ZAaf\km|G})bf8*,C;%2*Bs sΕD Grpe3 jK1JȲ+O;|2hh̩DH-ő5\A(.`&FmW(TVΏOroP~oosccp6rCZZune D}:~:`ؐ2$!ԹÙDB(&>! 6!'^ k1=YDEKTNĴ !\4ٞlR8N5$[=k;Qo.VjmǴ%tԐjn~ڢ4/gsS>=&Й<)`CAA>'D  Uzq뮘yF.& p?2f-JΑD8?4UVPwhxT.C/p2:D 6Y:R:iZ1mzG Ah?/(M8ԏ$ ay$`>7O0Lʏ`Ow0"V<}vGUD9i`pҭ^NmO۞qwvRqyQ*b@̹@F@_9jydxބ!Cc gk\W պ̑ rޔLQ3dIcpab'+cݑ*\z٢DIIK`o+/2>P.J*OBrӯK:0`H94\1qmI)-ŀ |d?Oi ׶m"O>ծ2]c    HP. l^FŌ?.DTpxI;Qa31g֎m!et1˩sl@_\\&!d.qNpɆ ڬgj[,e -%|O·hjvWUX'2% >ZrV60蘜)$l$%#&ܰ{[10_pL4đw :%}OYW$#r4;AG tC=c}qʝ1$ITr1(M1]AE?Y_!DrHc3A<dž}wg/{ z9l$c4Bƭ\?̺%SM,8EuMצgU`,$c/22NI]&>uϺ &C 2iץ EMeS'Qk:݄TT2 {$r=-sO!"(}pM_hV)s`$rrGAAA2(x2&AQ`*]cb.rD,JQ Aa\.m !*1 ;Y"b#7=%J7O{t)#$VRpʍ`s.c;Y.C|p6-r9)\N +0MC>fJB>yU -)놴CHd[%n\G@2ǘ .yf,BW!AˀhjӦ݀]K˜&>[qRd"'sAԛog`0 atvww[ͦVb<ßY*V?rO;yPL/M+1ƉDbEURҳ飁ŇF &Xgvhi!:F <NXx(B2cj*ne|N:þwkXװdRfFrp   r@ [嚾[~c̔CTDB"?G0~2e(.%3!A\;FP.sm$eJ"Q")zt -b)1YCP\kΡ%B伷i0?tXw˜C_J`1@4,[q^ӷ6}*G2 ڡ&#~֫s|b&:m8=(ĩe0~vk;[.9O$srj_T{ < ~|ne3u1&/8ʼnՠ_.{=7~g>,i(iu>F+tqM$yc)}v]BZmtTT`OrGi? KeCl(uH^MɤdkLejG2v(5uݹ`r(7tk\˘}m:E[⧶%olieu?mK;;}駟|2A9'}glrjjX\t:;kO>X )oY ʶaӂM TӵM![ڞs   J.AfzM3q6eVQagfz=ӭDjz~2S +{GpHM1/j"czXǎP}mO* c6)6R04gFq5A~n?!_7> I뜖D!p~P#dͭ20gLڮq 6Uh7M )c ö3Gv=K(V4Y0!!YcygY1~QJmnn~fs7`(fZpoخk-テ60pZ凜 u 썵^TJ]ʆ kB0ױv==jՙa(8ZH{3vs]B=B $ԫR?,mHbCYuQuI4/J1-D¼\ z@Lv>3U>-ʔ4}"GMmoͩ(4hFo||j[jZ=Zm-^*\uI{u:~߻WTLh`?Ɯv9pn[չwxp,qmG\3dCAAArAzf핕 A.#|cmBIfFJj;n e\f1i&af_Z%  sRYJVJ_R@OC?I1kOrv-'užO2UF[Fԇ0 TzˀQq6%d#GI_0Ӭ85cmW]$tՎ2.š]NnI)q 7R)MS82 )`(#&T;Ա;"VVk5gmNJf`JR2PǞVׅs`0:rء C'){~VJZMLj5)4:,!Ing"-3ù`m MsNp~{nh+» NќХD,%};6?rp1zTŘTP坷L .V]JwKV" 9ҩoMZIno_3n$YJT'b؟q[z=B",\F?Z~ic5' ^h$WMeNI8e\Ieuf3ڡ\AK>5ďBLp>-x]J&%!N2!QL"zRG2)xR1! ES3$xuK7V:6q"q:Z6Ρ{YTQv8m#Įàq&݆;Rn`a)cuk `1˜`~ xڬeLCKv{נlK>-ߑ4nBъ"W~a<1;=;͏1M= u9hAAA A /_; er|`\/Sa v2'yyeݞ234 ՜|)t ڑPA(av%n e֫m( Vvih@QBU>3%j̧; s-EIM41ZߘRBH~+Q/ ѐ:% !m5d 0NO>[hW!=`'<>G vEȃaԓ`I|ӻy*뽵q. kqJCee|k-H{r}uSMș,P{TXQ3~plW=3/     WH1GE9)1f V˜ލN0ye8 XtמGNb5ӂːaA9v#g%93)cϙM! cqBi{}̤A"CV&FKYT%3>RӘ1CDNlDzywRƊ9d&3= K;!"FSp`WU Gc5.yB@ك"O}hJg%bFrn=:F=g.<4r-<}AAAAAR0B/$rZd4wnWǃ|+י;[Scl%wJr*Fa Qz7׽˓˸06ɡxbnDz:AN0k-&^1SEȌVIZvAvg3'Wb)MTYSLt؟|Zy _؎6]k3ć eSuxIirFic;>\)neF$#1)dNYB m1~%y9vd_{gr3sAAAAAy.^L[v*SQ ڑ2Uz`ݙC g+J>Ǚ:F_R‚(yP< EIh(֜0J'|9uxMdrNF#'2CЏC%AiB}L 9GLȤ=gl'OgB\n}CUVr*ٰY]g=[6J.PZDŌ`hVڄLdaM //P|f3J7h7qO5\YpxPG K+s?#v|~F!beǫA! x vFAB1ccoD0uf7s=_e5S/ B:*/+F,3T@9YX2[KyՄ_/ V9#)}\zAz!2v&X2d3f Z412m1>rLѩӁ ~Hn`Pv|' AyHB̐~RV"!N5E_S/ {vu'wlT99\&mdBrzuAC W *,{ z|K  iXG<$nbƆIJdHDr\ODb"nI1˨crzO(|%Bų#r2Dxp]KЄ"pq,dF)XC(CşXzJE9GA  OAN+f|T(C 5IW01!g'crdz=VuJ?ɼ\F]R2&R?-BHFStG.$+3ٱ,*TvLlƺ`y*$#>a#]6mvgϪsZ p.<{ kU͸ c8\{pW>Ak!ՊO4Xomǩ{/xcE'C,8%Emº4m`St3WF paӅ>5%W=U| N!~\.ފ6Z g 4h0N(OjpIhEr'40+^aVig$@߄ڂOF[b=A^UGNR>3οg;wn\k X$RJg^RtyeএFcR0N +(`\}8iƖL;^mh@l@ #|P9:MI HBDFs{f+/+,'܆vyjx?a ;]}%_=m)Ţ&́DXdXy+ܜ` m7_ggthNJbКf|Q ,gc'hrQI&D>CCЊECtxj˳KVO#tA r*J%9.( [z%/~KbݣI{9,}@QxAl-ZqzpJgxV-:J_?\Y]I2>q\n~V;WnJfYl4=|xm"WhHVz?`aqZ3 fW__AOMl1`uϟD.2isa*dָr|j =p-e1|mzy߱*gly ]^IHaY|. cԕ|JsS%KcV R[j9QAX\ZoOjc$sǟ~G&uhxDJ^q޽Ǐ~\'ЮoA0RC|70@Kկ x8 ZYS/8cB`BAgpnk kny$SSSpwǢ8}bt:;xnQ9/Q {h!%>A}gff&''Kv}pp`0A AAiZ.i pWz^Tޤ:{%ֱ( N>/^ jğkWs`[ u3 y)QTܾ27F9?{~HNI$pZsf=ӎ\b:8􄠋 疓ܺ=mvr300T8]6rv^U˘˭\pK>TCT—Z4cazqF~jXvBC"ĺ*h 'B O,6" &c͐")Kn|'(मw}6fs8dj&&rye7;w`FWI|w߻nϔ Wx̻;JH**R ~vv ?|O?!kĈ9w$(eˌV( UI&7X:ɘ9` R3JXq=BrWe_c 83OYE`ޛe2ιo.tNNf(I0_V'5`>w1~<`?Cxv:IB|j{{oiI?⋃}+褅[Вoݾna3tʤTjKD>a5pJ;0|7ce'9 1]%W$i}&2DX O06' 0kQ !oݺ# sagU|:(̻UJ~ 衣vb`|oooö`c1  *J255N 89nErL\8fo9T(ﱎ1˥Ա8*Cj ]&p΂ j733.78ϝN'^Bk~}# /c& er)ey'ה\[8FS~<{Fta`ܞ2^cO~*.H1ryܞjh Mv|*qfٴu+!]i8|GXmoz^/~gҹ%.3(y4RƖC%;[Z@k-KQYN,ɛ8[ ֘ *Ɵ4γ,|7|s1޾s筷'VO?Ɔ\eDZŮ{ړ'ncO?SSw}㭷j#6 %{gK=T1q=gJO3.ɜLJN&DUc*Rr^\SKȌ`V/)f |W_}l|0_677arZ_|j6---;|=$~?ӧ[J P1a NNjfH+޿wz~ݷzY_[;i 9N3![x{t`}bA 7q&''fff2ܝɘYl3΅-` aݍӂ\\&jyjJ u4FĒj;wqC(?  :lE6Iplrzz^'u ~/)=TG,^b':(ڂJTjs1x(*fA[>h >|eɈF1MHt2tX1@׳1 8׮AUh4 d<䴯ML@I_zi.//>z^#8܋\mI9㺔;Yϸ!mR 1^/ҀǏ?쳭MX255Y^Yyw7R p,Uuw}_Q*VVV|;p;aA` v{J JYIm9A#A>^e`$2 #B8VCXbV |hqq| XtF\ee$ԽZ'eAjĐ~seL 7i9TGpJcvo ~&8E,U)ǃw ȍ)f⋁+I٘eZ{!=k?aj/ b19i-rdإV6atN2i.QzW O|F4 ԍWzOY:\G6XhU&q|ҧMF[9T mkw`|$ʒ 04rcrC]ڬep f3^s>['9Z9XηWn7(gM+_Z^KKةwLOO/,.NcUE`: (38:РKv&߂`<_gDa~d71!ai")a8XӨ ^Mx3BTON l{>gɍt܋o`r0͠;4.^J ~ڨa7 =Z[o_/--_ WCz4ly0f܆Yh333 i=ap!dp =[Vn$X)΃ VBc6(rd$j QF)www766Ɛ-տjBXh =EZ2  qnsR.  ?ԽuDAQ8hQt)a2"eɾ2|=w+ԧ+ɋ ]m* rK| Ȑ`ˉ ][ص\odcOi`͇ HcG2'LgP;%/rÜa3'(-3ZdOp|Q%Ng-=mzj9&õ>Ec(B%29go=>d)rh?uɍq~t4IX qno7+=O~,:iZ)^7n0aFR:~Z5t༖bJ2p[kPVYKΧ}1b#Xzr&45.6᚞9ڮBƁIDvȍ6aqej8 YXCVv{\bshUPh8|BTׂ,F:88R8͊*xsU*_ollܿss3YxVq V *f.qJ]5ژ,/MS(" !$)B1|5MGKN@qTnG+x3phDA*<9>v~{JLs^`Sxi,O9hBy>_nl\"7x|0pe5\[D$seRJq7M3F·rvT!|L$fJXMc_L?/%b"-gij4×eE\%%*0|J8|B) kX7A.m1xC<9#}s$t>Zn>/4D'lKV?]2ۥaQGAs oiZ2.j9{B$ױg`  ~<=9Sdϵ3sΊbR^M{3/4G~#\Mnbf al%wJI 3Y̮6$,[DpOt$Frsp.50#'ap`FǿQΝ?D'~[9-E:@vr}AԂ@`!ZBYgSO'Ƙ4d0C~@/p0n`\ØeZ>qVnd%W3oMC~Ĕ|v0\*XBS6սqӰ=''2699[oYǸRf`0Rʅ7|mlooCF'wyG0go0KM7А]|T.-Hkz :qHn,7y1Q/ ʌ)gMr ]T'vAl8-`ORp|q'b1sB?QSN{h-ryuu_jyyن3gmۅ&l6 hrބt67xceuU+jpopt|޺0-y]00 iȈ12b&\Sc:vZ7&7>9ѡ*2>4I%jgJ}OK2 -32!֚3Kb)[Yc8竫zCnW_|sss()T*ryzfC࣏`aga@21o555l4vvv>>t-Ij695\XZY]!k}lW6F=hoM[LdS8~vu8R n%pC^RSlMtM 6z^,9М~ݺu{0x_l0{=36S*&&&ƛo\XXX_lŁ2oMH?׿j kjuvnW}@w=NNbFoA1!Ub@1>׊ ,;)n A%u:$g&H2x1W|Xh̏yM⡣~Q6rBbX! 팟b:in=RAAm۸Rv#ǞW0;PXߣ.)?~iUFP19бxGs::G=񺼰niS=tӱQiwW'FrD3.>V)y\{P.r"nrVtdt2ks0S[2|G(f{26H.3'y2*dWVoS' tg1P"}⪆6v ƕ0)IʌAS/sFT#4hʴs]c[2c {t,ˆ :1Bʷz+MYRVfwo{l#l4KB7 1`JC犑 /1zu|J&E)ԓ'/py|2mc"zwHDM\" 4l 9^h?`6{>4!.*cQ^˥O@{O?y O8 P0@>#DryVSZ}Bk݅ )4q5x'Gmy1aYxhQk2θ $&{9|iGb$I4 !/1+.י0!cvW*zK`'rv X)Fd(RXXc,Q[gvR#~OBa{!OqFT * COR  rUo7..nxӎ/z ϛǧdqqr20w|?df1 $E("ʠ w8 ==_X{# sոZxwd`mS=[Xg-g8rş̅3p~Z(݂S-u>fa4v,;1S t_,gۃ6OYܫ¢y~:oZ61OfesgYF=l?$iNNNrgff:lxd-ϲSMoz+F7>Ta/97G'ѻC 鍀Z? V2kZR)JQ4Cb끵0QdzzzbbևuG0,"D}iO1ET̖----//Ó +ncj<0.E}I>Q{Bea'PXB  J&N17!_lOBT p$7+A1.?0p_bFA+BFH!n.GƟh [ctRG! yɈqntB'Vqy7:Bѓ)3߈EJb2׉F1DOPŀ/zy v>8?qWO5^B7iQIbޢ>c\C8O'ԥOx .wZȦXi6Jy u/{xQ"+fu%H ɼ\f`Ϟ2Jj*ř{ҧN 1A34Ԟ2 !pMFP2-r*CZ8Zӳv+7  r\4]JU9s6#p)2[(1Ɖ#Mmnӳov: (Cө! "Vu5124ty83y#LT 1tuEDFj-'9WIۇ=Fǽn=~o)E¡= wwv'߹{W5 I^6*mZro+Alg{~ TU6-awtGh/)L y&ʏ쑨2Po3Er0zڻnRJB'wFW6p*?u&4U:ɄrxPvBh :d4v?|_%7Y^Y_A QX W~kn>|7B?0w/~~}mn܌iL &έ! 4[KA{zg?.3grZ6===Ơ)$dzn~:z}eeeiiZBuccƹq kV*v.JjVlj˰(< |mm vGAхXYD S.a5h4vvۃBuE8nnn.j`I䈞nǓj`?+zqqJù L9uR + 0E,cA< qp'"Q RH@` /I'݌B{f̿Cr CKQ1/ ˸G[必ZP&P6(j|ߦĪb># GⱢ#>28*6=QdEErTD8]Ppa?C3;Іy<3QAs4VA Q{.<Ó.nqc,Nc0ձp*H *e1j#,>a'A| <Y}H\kqC)I/sIVYe]Mmrip1g]T SJ~; R}cw1<X!\N OƴD:ig 6-8mQpC$ IOLP/ ]@G6]c ow~ӃOɏ/מ#p ;ͯf[^Ywww<~&A ̑>GFO~>x~ֺn:0! 4V9K>k68<\ EZZ6778??+J%Nc-f$CGq~8 2994;; <\Dq߽{wuu6 kS"fbCSq Vt:dYpzz:ƉNlp, ,{Z, DU#w(<:ʰNIQ.C:o߾s.-,N#AA QtܯKGYC|Bѵp[zb?'_9XQLYw/WQ:CLDIt k# N,C<\ܼ?CF pCI`o"'xENw%(ȆDEy FyN~Q(`>@wG+u 㹮|\~p FiY^B^kZF Qxt_}E^2CbἍ8WδRR Bou>LAc5Tpʬ*n^T̏ў8~ć9掎,^ νiᲴm*,'(fBt/YJZ"o%-H&OMoGC>}1skFng{k 8i<> \_]Uo4ehJ)'&&~?/ڿ)w0gUfRz'W~>V!r1φ1䘝O֨G d߇_c$(p8ͺHc*9 &qo͋O0fq3q K,[^}B =,PL*8DU[]]sN$IB8P!aX,eET@@3 q,&oqt(p6 9AA z$1FHQfbkx`A(R8qDƥ$;cQ 5.%OxH]8)"D(((BD3ų+k䳺얓P|VEGt T$B:J)$$ůG<Eڝq=wztGal(pgu<“AQ* # )Nup22/PQ*sc3"ZfEp]3)R2"dt]zahqa?Q^ .w >sH1'r%/a|k? u|I 8iqqFwrY|G0> jU906)DM0; M&qH'kgW(g(8]7I}.B N?0.S,0j^C,|jR 8|9rIu5gH >Q4xc7K?"EA#wFfMqC:` 1*c~?}nG=NsfaG`x^i}\> 8q #;o[IMzRgz72{<'ڶ K!<(7;IHu8}* l0bїzD'iOl #1O;d)Cto0fګd\ƕ'0(ڡukJv=dnv0 s1?xϝ c{9|]7?@Ϳ7{+?:ɑר-1=?L߬pwwKeP%>J<@ bJpf{{ŋL)bX~ss:%'Q`]؅UdX2sJ7-;;;ϟ??88>oa3v8mëf|EIC @i0Qڬ/A@x_q a}&0Y#þlAŔ^P%@i|vk>Qas*[ABUOUbp h#|=ptt@8K$7@ IT ` wR\V@`I'TM䭣0;/Q|f` %S6*3cXCY&4 l8ܩ2AFb=EDɰ`KB6`*3mNK &֊囶dEqW%e'\Znÿoop,Vv~ϡKnlN6̛UTQ3oY> 2VO93Y]g. #ǘА*L?R8eEmF wD_Y3jYmg3ИxQD1wի"}*ʟ~hɸƂuegstouؑ5 qnܭY2lETy @M!-!Yn2̒qZC{eV -7r;LA~ JOUAG)9Br]9'ÇIm/OiRjQ!FLR8QF8ܩGU?VgO?_ ;!_0N[5ܨyo7sh sa2}/c!6]|=/S:!*dl;3046ƐL3" |3Bû qqG0܄Hr[{ |JqB4wVBS&rX>ܓݒw!tA~vGe< a9f>a SO?~fWWWt:}=o NLL_xba{ExE3(Pܝ޽;??o 6Riی0&f3b3"6K@ 4&ְ̛7oP5 :>t?̞J" ({Q8B_8tM 1&@ P 1Ӆ*,rj.'77/oZGaIڮ*a5VrE`_,P7Ac+FNi|kka ?&<\Hov?LfvWLԜ[?+ӡZ EAX.OEO0h3s7rS5Ȫ4, ՃEP X+<1 g{Z?mZ1FI~kLIqHCq/ ^EOJ:mztA"wFpR|P`1>6⾔k8%Q6 ?ni!6CLhB3< R 3kdDBwĮBzd>4gJA7rȏ0f7c5/H>'ST@ǥ)cC2ĊH3Ш>0cny$hӤ*\iC63I!eaXr="FߴD dy>xvkI pptyf6R ݭE`@-ThHIqQ) wZ%utq3mk6Ǎ݂`iT8>@r_wmuϷFO@]Gllc&5r`as@vActV1R]6*G)mQD /hW!^἖Gaz@ayCM~~槷o}@P>ĀC+ڇz0\\\ `W_=WyɀfҒ d*/Krbjju4f_mTT'VbڑfYa@wkk FB3a{~(%g8{gϞ}c9>>>998Y"k!2<>{0mݻw߿Pؑs*}jB@ϟ?g: l#[V@&+$}Fq Ü!%|-g @ X{P2}L-\Z/HVs:ݾm$0E gF?H+<`&a&t#9`#sa8|ϙ5Saʪkfa1xږ{TWvY _(bB :Pk|ĂYeFӅ倖0~ j%Ss8dr,þH1S ⦵Oݍ⟀eQp`n7L"9RQjgQjǚ9IgԬ ΃d2ӊ~% |&?|qqq4&*O%"F"ӓ~8#`L?wg!˨~4?&"Fg`|AsSZCKmHrI"8*EU93*g5$>k={!2jdZǿD@i=AJ'~U=40E^jᗹGGrߝ~O7_ӯw+]U=߬'_t2a-=';p}xy}u9ơ`$z/0äϏ+qlr|/?vE xb*cxf0iuIat])3SZx 4<`0`AaϰU (<`꺅IYG@a}6'ڐ'''"-Xr?_~+Qɷ${0G!!9xoW8<+~L#*U :o-X# 3md .900 'd.j/oz~֒24tڀ2d)}z車si.fpt:6ǥq[Koa00OJ7fh2ݺZC݀f3ftKy0?wwwsMpj݋//#fٕ6X,`w(ۃ9V=k-mӁ6mǨf3 %mVG{>#>W}6S//b-!,T 9! 48) M+ ʄO+/@ k8ADV8'0ֶ%eAb8xKpvR LKkf a 3La TB#vrs9i'ҌY#-PP=oX[Qimv76*B/ &D"/K2493BWw}e&[w}wa,d_C -7խ6HK8wcN[2G Vm72]nH+%uI]`M43`.Ͻ oTa.SѫG)._#]Ww+C60vyvNb~ /#mWṋӒ=X yVG FVE $ȼ$7pmllj`vd29>>nYʅ휠^|tt~L݇mnoMp;i$+n9BUON uUqyЊVM:7a2 4x9lC@&! Cy3f+x2/>V` p꾴  Z4` i)-krJpF #6NVUFH+M +{11P+ym'mj4 c#XݴG0[ŢTǢmKO6 ROLVw-I+XBqI].TWLbw\BG` d3lsc \~]U+?oˁ7}] ]fj+u.۲ 1]7[L=|B:QYMhC~LFd儬 7*@k/'ęhȵްv7(c:4ge$WUs

8 9KF5RHR*NyN5Bەv{F$z:xMb_J>0 /瑷l&)^Si7Fu3>O+S RḪI"ܺu>HEAqC 2V+'ظ r"(hjhMg_Y LK>'/Eeٮ+_P֮ 'QڜyU,ˋ vAjil|wwwoooccKNĪLQ?YXҟd&0ڼwd 6ë۔ sz`^\kJŭ @c9+3777''';;;,{?zzi6u%v`mn<YT# 1BV3F |]ʓ۷1O+ҒЭ\MXmXEr)*S4X6K%i+=t9qd%f[vIEht:mxk=7j9Iv#SZav t]t+$k'd23ק< L>KtV,|#Dz˜=g322LWP˙ ]F‹ܽݦL&Jݽg̽ӠD /L2$Oxi^ \pZnZmІV,BSeJ%t0+ST̖9S&Z/zBu@ ?1 hɍ,_EtB#6NBXpD#NȘ8[6{g̘؍~e8k7Ӎ(Kx$%[@ 3Z~ /eysBn7t [{ΝdXpʇ X,{;kښnzݕm9C2CiMt*Tp (]x4kRdq@ >fBK;Tc8 ]f Yd.| Gx-Bsӧyr,"?[ٖ5I^-V;R7-nK7nlmrϮo*ݎz8w#t;R-(}9Ȑco8]'Uk~+놧17z 2}YM7 sE6v5]>x\VebBɍMkN2BxP=Б6#/'(pNϪQ6 ubFٴvlMꁱ Iੌi$DS ˘&D]g (` Z:zҕq6J7vK|]{ʵpE a\:LcB"*CҘW1$Zlԣc,fEҪ1'ԇg-|Er;Hpړ,8 ~rYEYBt@DŽFOaX߿\.(=%|ݜM7TG{_5KIӥ/c=̚|i;RFX0e6mGy(㟻w0 #r82cɸLCaz. pPptV&WWWuF;(/Ew ?]{ЯZ3S:@F%<܍GOXC['e̤f^st7`iqYG (4|cc`VP6f@ 8eT|R莿yr-@` bI>dLwV=KwB}Lm-r m$UPyKV߰Nc:Rw>:2nm7n7 d[ Qlfߘ{Sd.T)ϪpRYL-rTCek9+Q*)עՀ/ >45MF!'kɜܳm G:\UXr^MX6RDXC==v;X`)o,*yHB&>#z=Y2Ɋdbt~l2 /dJSo s 9OA}L^%R#DS˜Fл , ELFڝ5fzbMIWF6cB1>pxkJ bwB®V}Αo$Цs0h#5FYc2` @ eTVAzEQzVDywwJb2.//9sϒf>ۜ[oԧ4PgȢ{Q 8߃O/`%LqA`9XbiϼhAf20HԤ~.|ҹSt3f.IR 9N8raNfwipe5X泿!T x@ `!r9ـooo흝MS=U c})x<~ׯ={Vl6L&Ph4{E$L[M>%ɬKZ^o8@'4+ O'ZX&sk-ô5!pjE`vmxC@?@s`2@$ݺ3CZ-1sրfpBnnno2APZk Z?2W#'0c%(ߍ& ,0mGwolO#u]Q=&m{aLZ@.miLEj|`.2M)d7<:֔1C(95+1UuV2J}w} Hz$܌}VSj7 ~#=.xֻμNAf!1r ~Hٴnef0tc Ojddm5OENL͘WWqYWB.SiІibEQ"|TO `\-I F2f0fFGˎ#$BAȱ )P O6P}sA2?bW/}bPU9OOO+PiEe8!+}[m^J _z/_xb<WPl6ڂXv]Ve]z #r]pܶ7dI[=ކt7b_8:46pRs%6L&m] @s`h/ʁ}Y0^C_^^|oа#w/%`'(Z!.סBٴZ 1Ke{ؑ2q<(KR@Zj er} ieT iÛWloȘ~ji wwti8eO4268pcˢZ6G܍]ZLWφh. 51fZm[vsS:T{璩I-1 %_He ̠?6PĘٲfWG?|B! @ Ұ k9!%ף'd8UZ&%F-iC~Ϗ9(`ǃϟÛ,/LSmJϻ )8p8Tׁٛ?*2łUݡxmq†2l,m+8;PU: s8??_\\W@}ɜDἎ@ 4VEb  ˛AS^X &wX-뚰]#W\G tuVdkq9|tkr$sLA}Ct)>\[@[ q4Ŷ}ZB}Xv WՆoaxڄmmX΁CpB>jEo9l[uUm>[{j$n7B m7ʓc3af]uEҿ]'e(y\Q0m8"ϞnLk 3EٕdWYu۷ G5^xThAu9F٣}-J GB>yfW Ӈ1[Fm#,$8c'>^g,yL>אk%fQ3H"B =a 4+~^7(G"3<'J5Zq6AƷ@ Ȉ5 }$v4* _vER}զ9kڵXk1e=/G* L2bFS8Qh鏁=[ s=k+nB9׏?:%р$ɯO%y4&P1,VN&󳳳 /qyy9ͼdW`:99a | |roo(|d`wf@!̈́F+ ñw;fΎc| Pׯ_^L`2 @ Th࠰ _]]qpbckk맟~b 4}9;-J^t>"0m6c!(҆dy@ M1gB 5B| |s0{ԧ|»y G#4vTwl+j5+\IVcMk<ĤmM>CsXم.gÂ|]ˡmW? >aBF݇A]Ֆ:oXYڭln ՆЫ,|Rs/F@VX;ƌUjEp['UTWr^!x]ƚyEm:kꬶ~U~Z(":jaH oq^ 7vڗHĠʟʂ/SsᴲN &0:GjסaJ C)-jL 3Lk{n45vپ^n)` T'>NcV0'%;KObl9Lo%VN^[BAhE*}ٌwRsPc gΟeBHZzmH0'+t6$nbT-w3;j0~ILnDaOrֲn݄3L766>KEQ<{잟3MnnnuzzZUI`W^xayrro%e2 "锵Nl0]fA7> `PYa!q`'3fТ...bP^= =08b9T[2@cph!Ԝ Z?+j 빡 l!n;VҌ@ kpTGf)03%mY l1 ni|)2̥hX RxWNL ؞k 3*ƕpj`/XuY}G䰰1=ZQ\k;)Hp8 ptw~L*>V w'êsf8<-[U[l:2mi- `nd6((Xw[5bDUo[S/{Yd8|DZ=|TV'e?Nse^ٛ"ɜ]X]zX?MnaiMrݣ JaP]xQ*iW<RU1]UQ}S/SfVC'LXɦPWFQdn<e0ZHm`͖5軄QNz ')6 ;MDA*6L,p+23=3C{Ϊb!%0fLk 3Pr4YvU:@F.)~p>k6=ikF&\]] h4]ؙYV!SRk6V%l 5d 燘dCO[J=&8aT 0ׇ3P^v(nlC-]ݻw÷lJQXp ^R %!C، CPp?O˜`n߈4>;- ̅my9  TCaֆafiLh.]#"9 Ljz\f3 f_m:oɂ+lQV6c"۬鶱[b[Pb8"k r[X]]  :=' !> ֳbs7VBYm7r3lՕՇžօ1̛^2d2 }Y/QN>e#Ӕ,"]euCEԚ1%r[͐.' RDcЏVs"O}^ :,N]W[\tvLkEЪ֕aEHpaNBxK\_%ڊ4PCgd[]MrU="ad YAj+ Eq:2֫xSUP7X'Q.("Cr.LMAp 4,Ww___('{vww{pwa5~( N{f><::z|>#fYlmmj]8('c8[·Ph1wX\]]q*89Ζr1_@%Y$/n911@ᠰ%'f@:͘~#Ce= %/ l r2ٺ_ R uc&UZ{"0sqc!DLDfUamֶJ jt33]-+ DD]V.8]o`'>hሥe8~㚴<mKV!]X0g>K[5 k1~ 6 :7fQݸ 94ed:_({t㭞돵`p~yÚE )޿[+RQ7c r`. mEa@ $bGA0_E! O0fy9X`elt Wː`bk02LڀX~bJxρZr,F}iM`~C%[#!踶NKa6M1σ]wG-=%Ȋh=jpA5Q('GMñ[upL·Nl7o7mqݲg|,v̋2ZZ{V6[>FJ\&TۍnhY&'?~D)QQV{{]fuW 2e9VؾɳА*,coI7g6yg-LED/*,hV1Xɓijp 1gtVUgO*2Ņ7(.C\KkӢl,_&CE5fڭ 44V9f0M?B>f11,3 ]ISB6"3oʍ15J|2iHpdOJ'8)UȤa \/A*0MBa:?h3m*Q?-i_H%%Ӑ=J 6N Lޣdž `)JT&HOJ~!TΫfcjlo͖%/t TWWW;Ȑ}8KFԒE>p3[P8|*.Qo|á9]f/&-|E#jd/x y7Sp>=t]jH3-wK9Y‰ AOEޗ l?=޽c*؞6-I @3;O,-sL`mr*аj*xoʊ1c.Ճ#9s/xˮҌjt c40߅*&j >ئk|\jN`NNEAt)mɁ%[VsL ii-"g;QK[2J&PmU"dFk+olɻOm72釫j2N{Syޯ.#P< 2Z=vdMh%&?U-0'4_QIEX{l:EZ2djdSjVQAxxU2b)w͘ ؝<^]Q4I[+nc7\jy3aSvPMpx50Z0̈́ ؑ9::D҆`JoZ?|#rL "6@x.B ڒن:ȁ o+[QV +V=H (KW}em,:\2SmZCɶO8hKfBLWfpu8ͼ/XU^J]6byQYΰXoJٖj=W̮S[6x㶿iwWhgĊ8\O5ďd̰C~f( `R>2F-'jٟEᆶ)8tMEg_ HPR^ACyI)/սC~0ШZV$t᪖1rv >FJCL>QFXT#2NՊ2 P W8a\G>L Sz]S 4Ӝpc{u̧ p*D4ě,y@)gduZV@`ƗBH%n@F3G XhD#C*)eUgIupBmŊF&eDlf8)L׶%{0JՀ T Jr7ٴnOI߄\߇!TFQ`b ーZ@@ hNL&p ת{N60_uJjs0L(iSA ^a|qq=D7sЍ$9s}}0TØelԦnm.nѶ4膯reNbbH˭Q8 l  n҆7xŎFr@ aX*0;˷- 386`C[T6x 8-ݸU7lK^mE-J0k!myO- eX'76nܭO(wT#uZiF>ǁw; Kԍ?oU=nlϙ5m7G+Q x2?W_t_]fLuM/{gL!MzӚ^=^ٶ3QI<)yTa>Sm9DwCƷ1R-n *wY)$ /P[j4¸M|T' @Vh}yoǺ)wC[ԊO?H0dL>Eww$Go6>!uyd=V(mzod7FE@ X7HLa?9 ]^UTgUuY~OJm:󺗿,Ac(b]&Ĵf`>Q^5g_>f/sۙsrQYMC.' >u(̨\Ҙe 22"T EV9•ˤ|c¡׬dCmj)ABmäKUDbPQU^Rk>'[ 2]p^8E RbEK[ysDnԖTnꊅC6QbrӏgI(  F8!ĖD3_aYo|C0QT :*'w~C<6ywKɯ>{}OV1)`:e<ܧȭy:ҾGs9_~봇Co+:$V~cUXwe1sZ_QYh)gf8ɱ^uqe| b7l>phAu au4e^٫܍L-4zhd2ZQme ,?nN#>yIt+&8#M>8ڜh*j--Jv: m2"U@j lcd%*?%*_ @ hK<=o{=/CUn>ݢ_{@=v3oE ]m_`|_~ui_~zG6,;(Jpɥ'ex^*c:/ 7 w c:,}Bgfm8ÄģH9 Ky{'l:ÖtAKC)Kp6lX3"Zx?152N)V1BAqG2G7T3G~Qዶ٦@ ~؃*vd'O@x ؏9~V11!^.H]y~.ƟfUL?,PegeF8*w7k O.V1 \6q(\p7r B ] Pon[{"F'8s|!,'>^L@:2*hlE$:]h=vDt,\MHaGwS_n eLݗ&SqD} Y _a$7 ^` m#PW"eѯ0(/$ 0B^TaPo0Is-Y+aF3FTƬ@jo9̦C*`,%@ @ X?)Vl69>L\ ʘi ½.͌TREg e>ZrShRGI]cH kl-4/Χ)>է]74 sE.+$3:׸&~`n;Ә愋֌,:w"@òK=fȊHqVeRN?Mm;d,@ 5YA稒4j͸t@p )(pҷnV I9ֹ̇fn1fuS6&$0sEK; u "aΓ2"$vvHunLFCI X*i exd@ @ X/,i9\.{/@ |c󪗽) V儖vY]T!ψ=.<~eZ ˜{'ߥ(/s,T1ݪ3z//l~Lji.ɏɈJƟm(GJ,-r2=\[ )`NI@iYLʘB,׶P=f۹k` )zKFwZTZE{㱙+bT5+.\.@~R`x7Di c`W"|"E5-gsSc&«$Uq%~5'aqYCj,ym8Y 04^0khOgȿ 7&@ @ ˘ !f3Mv𾪪VcF\; i ceGּ̳lټeeeYT|qM=\ Se`VgnâyҵeuV}|jӘzgfy7'U 3jTiڲvL=΢CGMy!I)C BO"\as|a^u3Qh&t[ :Hkh"` DT+#np+1 Զ ?BT)RyZ:wD7*MB8A#OHŒ>ķ.Ӡ.6Jϕ04vh='CR".@ @ uSdUUAE `̤z rEZuDteteJu&;"؜(զNhNWej./,95͟N8)ۑE6&e0B:O|Ld#[9G -lZ;hScq}Vn R(p\f&}՛m[;hd4S 2 .IO1| u:m 'vo58ȆU)$!f__~HO5\LU 󦧑h#3gS"\رfA6ּte8 :"*OT$1s7at5j@ @ @J)B` |B Ze֐1f0,E)* x4Qz3 28%>[Uǥ_]nЈ,sXV7> 7FU&dz2L`ΗO648VeLlgn 2˰Z;DE%y;te;/tz) 4b3uOQ6j c×"^H<| (iйp$OtdDZPb/h @ba*P14#czM(#'Ldۇ2fzIVPD+!T!)oHT0/o7CZG;0ƴ[|ђ ٨a6"@ @hM@N-9@;xl9&^"_p&;(uR!]F}֌ ؚy7\8:Ryɍe]Wv04TmWPܖ2l8[79"C訪|O#kk4erv'CE˜9Gj" .*^C,9tnus(AixL!%ۨ r_AMC42$sfF |dV^HuLX"-,e$8 >=2O4}N0l:Q51C- q(nb>; aEi33%@ @ :S Zcx3 6>]zwg̠?Wy6F#J25b3o $чe,'vD[=-TIPѫ _pgo~'/sXYq& EO~ E aBtM*@ė%>>MZ2c6rȒSr@9-UX!NC B2TX;W@ i$c Lb%eCg)clhP-vvӡ#KA 0,(BLpW8f 2bU hّ3dɄBR@ @ Xw23c37u/cji^?D:\!C}.H]fӚE@_'(Ỏ.˲:uˠ/2Pb aJ>q5s;/̌&/ʿ_UdB|ҡQN1C{2F mhdsv31-|pY!49eB2X7c26 fnjΆ3mFlX f i\=h6u2!C]3!SA,!D(gmgȉ$E6q2! ٳVnBpΞV}|aҥBѝ%XDiLFQh@ @ O mV2@=VZF4f>1BK k)S~[U&<"~g/;te~]9Q~3 `9"BЇUH?)$DТ^n.mkHh8ՍI!f1qQNlqnhT~Dy\lֹE;=) '? Ɔ i/*) 'WPmKLh=Ғ aea3M G,0ټ[xq#BY1!mpw13GZ>%JfaCKfSQ$f@ @ en7un,@ Vue>ɕyZ˜aN@k˦benH]Uu8 mgws;?\x\_ՙLY^OXܾ,-g,#ҀnޯK MKOv]^^np%8'n|}O+ڃ$?u^x]oZ̘ܗ 4@_ ۘkT)Bbu,M| JӰd^Єۨĕٱvd 4MЦ%4*ajBmI@%f|UT]/g@ gP ~@?ri4TT> Z563R8VL [s2\*ܪ }c? KR80OM<ʼQY@ @ 5ᓼÿ w 1TdQ$Z{ï|QFMg^9˴Kˬ wBn^fEbn|8*㽺8\{v}gۙ$ƞV (5iHٛӂ5[fvپ1>g R+#f|aC1o;+4?M^!Ah8]"Bi>)ƑR"+\V8Dϭ[3G̓M"83Kb02EDC _CfuTPC@RRP,S;lǖL0 -b\vpPVt_Q,[Fg8qBnMpp dJ @ @ Z4Ҷ#5}Jȹ0AHT״}AD%ҺQSڒo9dɌhu>K8]qJֽ Z/bb F@ X֢ 63ҒMUfħ84C&QTñy0 2 cj/Y%MWxtk9aZhXFT"G߫ 8@ WDwSF@ ? U{Af˲  2"Lz ]^CchKakuYGeuz%Z=e;&+5[hry6rɊkK}%Z葮L5}ʠq34^hZg2L0 5Ĕi`Rn0a*UNۨ)@$Y 7!9?aBjAz)H(d%Mђ)y"8_}>[bL;C\1!̼яxV#43>ҟtF.QkzFipZ03IN@ @ @ ~e̴閑1q~>Ls?_nfی2Ӫ:ZGߑv3l:(9pR {(fM}-eߓl U0X%tB ;eھN# WSG\_0 (Si^)i$/d' EnL ^ 3łBgX {yԅmBeDE2'pD1Foev׹14} Azܘ RqTJ|A?-S08}C:$i=8nԘII #Q3q1$@ @ aF7`hRyYzVf} ?B?*5EA9+"*ÕD5uɛgn,[pj >tn:G4Ge5QOLdδrL"1H"PQh䌶&_̧Da[%q)q^U>M"ƌ06 zY ] 0zxlZ4VTo^ a1jE2yÚ g`cVN@ _1tG@ @ >f~cCD4۲=!Uy4GQf|1StZ_WqR ygϲڏ *hUee>هWE-1_U+?$#m;'OL@?(LpN2HΪ@K(*s׬$,25p 4p=bx5V1x `.+*#G@+/!hɄs+ )&lB}ɗD1[nSL̠gsnCА6Ҟn߼2=(:hLN 5Le@ .DgF @ ۷t 1.@ W0LeJ:gi.ȴ^nٞa B sT~.y5)cA?{%gU1Y^\taYQ#`D)[PmelڡAE&hDxg9΅V'0S8i M$hukk4nQ:$5 8qM@ TFI0 8;۷,$T"WALeuo1fX.$5^Ӿ2t1k,zkeEm/tMNMߢ@ևdM0Wr}CA <| @ehBdĕ3X@ @7|̦G}}h/.ÔyKvY50s32/rT)TUyT#|~0tf }qBǣ?)2&Z~0iט`NnioѮȂ+/_4~*Lv++^Fسc 2c|O T*4=)JN=Ҁ8zߗ aQW)"3GZ2`XB5mқH%LR>*1tY+cx3-cTa|gIjs-Щ Y#EW>L'P@ _ذdA@ ~ИA D`-grE˗9*T0cb̆3/ E l\D-HY Hy\Z\=C/Q'kh 'UWɳ? NipBnYC?}%rLs ZJ.R#Ͳc%4d9h<MKol;NE.b”mJ3.**EV(g@ n@sMPaeBT2~2. 솳VsXE ª<}L^&*&>Nc Jh*;3wK*X(^_/&I˜@? job،@ @ ,*C.~3fZNfmmLSEuQigoc.:^\,2:36=pU*=`3fϹ -}8 JfKC &&evGX%ÿ{)'E+O$ֻ݊϶&1{ =f3:Hff]S>nU/q[ˠ k'_֙*>ȡRmޮҗjDg>?\ zÙ-xY gTY'qD1}\^6/ -e+r26HA^UfELJ`+'@ |ہi+r"0V1.B`gaIr .ygtgԆbBڂ@躈LԱs:ı0CA`rqE-@ ^' 3$DM3l'}%@ ?q}.2f?z_j:vyS/s4<\e^|At$VUy_ZeemQO*4R'ZFOz9MlY"=0?_"/E 7"A1reLOKK7GL:\a,dMagu؁#cRa*#mc /Hΰ > $A%/ SI1לf3#k3e-_VhUݬg$ڷ&!^T1Zͷ ђ ei8E44*'@ O3W& ]e@ @ -B2-˘aКddQƇ%e>gD:0jǡ!}0Yx򿑺LyZfv7CS{P!U>x)! s/ Fz2e^?2a]i3*Wpa=Vg;b(Zg_х ?:PQ'T)cuhj 6ZC6rr"yHW>\x4KhuLS@ a#i%ЇUfl~}c >Y;?GLȤ^$%m`DcM%ŧ6oWD]=v`%~ O=c6J 8 K(@ oэ;943y#@ 1f:,}cs'A$"E˝oBz&JՕ("\޸x(ʇ_PF m-gM JM=M7v#?A/'XWr1[ٲn0U7oE8oD!OҺk˱p^R kL*&2bB(Ci(P`=vlm`XIc)ӺgFj)0 3` 2#"Ysj`6qD~NHiE|^ g$aܷ QdҫzDEOU@h>MJK tM!ц@ @ BŪ=i,so|7CoPU2dXn`YFZVH7Z?2jڗyYTtQ!R>Vn7S:OBH]lآ9ȚPd}Z7 4EL]V*y*k+$hdƎZZZ@ &4ڀ^"ěVS%[/%fR?~\OJCp7(H jИѤ#M^%hTϘnڏ ]xr5fA-0U]SUaRǁWOVai]3V%'5'@ | k@ @ 2h0f8Ph,?89t*յs.$OyѴ w+? qV@FDZv=JX2m/Ppe#dB+7y$2[ I4 |fKt_#Er&v)b)r0iPN#cf"lI0l BA)eHĘQ8 iӰLCC(c%^@sx1[>YTᤆLlا rwfEk[rS ,hh,;#h AqP)IQhG5|i[y*PGC 0cZҞI^Q$f@ |㧅!@ @  ˘I?A梑.sZ_QYcRc2 5?v3ۮ̕oo!uL~L;e,R<ʟWa9>vffe~ŧ_gj+f 54_OpćY3H%i2ٺ:\8p~\6=kZ4n:kxӀ/BW19EY&k8!}xb@ ZMu1 ҭY c 1DYCdT ̄K&a%3`>*g9q+aDz(wq_ZkM,%c+Q'A@C4iӄ,@ @ 3fpE2~Rdt:-[#2/e6,+۪hMyA# VB:%Yka?2#Qaw kZ_ϙrce02YJ ʮ*!w}4 N>r k:f9ކ[m ,2fzԘE:M*]>e$'k7di@dk-"z0oe) 1a&LWhp(hV@:oL'ߌ UYrl <̌q:O:_$ ڕIE1jDM @ @ ?;>͘aȚ]6Œ<]fL 2faaEH'?,?e6/QS^>ݳ*sWՕ PX xg#rҴGhYWI?C ɵްvl5t閵[d0) Mp\Q&`p  $*V.`mȴ* :tl;x b'is2Kᒼq’9IQ@p_ӦёaA `_`SL0YLajsE`Rr֛ef8Kuwa~ XV 9&o7MN,"@PDM @ @ ><3u/7aY@Ha.s/ hZ<*+IMQi CO%9w|xgWK'̾ݎ|SY>V4+}+F+5l/CZI|Bȇ(%$ēҟU Inϵhغ z`nۙBk(C'N*ES&$sGյe1"+4i>Y/CڰbE8#hAIFdv1/2^3`PrQCiG#o њ"Lɵk7(MmgLN}.W zV[+%"L@ @ @ q1Se,emgY*!ӥQ}.~g.7,HYU,Q^kV};BkxTV'Vӝ<ҿ[VS%!)1;nfvH+ J3k1sjQZR:˜M,M)ҌSz=1]@Ѐ .9z+I Sr43g8J qLE>( e@ՙS~܁Bko "7Ytn,L.V$UJ9}C(LFKZt(Cs(ǫ9s!S@ @ @  12yȷ1*ÕeUULO.ֻ{] Y$o5ˀ2,/ T|WEv@TM+P!>{9e 61]UluM^Ny qflmߢKEfjo,V-Q-Sx>hTeL%>uChE ZjWN[x]1K]F PrU?=ɤu@THLvf' "h(32a ̄ `i;IkTY#Fh1t5Ƿ(FOĘiF˘d>@ @ /TjDGuUU"d>W(+Ќy]\ͬy'eeY"]&"f)܋<۰>"%*_]} [3BR$d>oc=v΢1C73Lc%g( Tts\~'ғD,!FְR4pӚ k{x_OiPlZ4g."^JR#'!/՜\ @A>#3>!㻵iP`'s"Lhq0$=Sh/dz_3SY/Ҍ ??t t |ЪI4f@ @ xH Vhy _1f<.Vn"eLǕeYVQ2";\jx|mNF.==hpYvGs"={t4Ag+)[V ohʧ YYI˜1{rLd1eQF}0(#T\T W޳JhdWZ:*3%sp%ɜhļeBP]ɓdNRB#`jӷ8dSISnQsNZ] I0>Y&=nsO43fuBt3֘ &-% Y @ '"Ws~~[uy3¡௃1Se2S=]Si.V)1ϥhfә7EF$f!‡WQؚvU*Q+ޯV~?&Ŝ!k^ً L-0LC^N1& g 4w2%[F!k$m2<~YL[%>1MSM ڂ2)FM:(":bs p/uDe+#x+Mr20*"? 戱3Jy `Jȍu 1aC#`G0.H >xȵҶD;x㥇.nKl旑[h ,ԁg[@  ?&?C+u+[#t@ Rpjp$S*Y(teuVySy؏2P"{ָz)3C9jÓV^u(U%E(u߾=b~CC&<-mUW'!=tCB>CLplX4*#)&!}Ƹ15MnUhnٴvЉoQ5mP&*,i@uHho@ SLSCB%!x QUgcKaekr7f;J&>Bu^n5aYZK4 fR4+0ȁ1'$̡`"|1xJF uN@ @ kOhc|Lg@ 5fVξ)ׅYk)I]vU<*[.^&&f8`]몺!<LHy<:BO:'O$?3Vsg+q߰J!cYJt1^v̆-QՉJ6'a1u$rnD侤xuz6p0(7t>P Nƴil*4̕62y Oiܗ*S*SFla sIuʎ둖_"? +i'ZIft3D5`GBVQU_B% RoD֐-hDbF @ YqYux)@ojL4%!$~R7>rpy@ Ć52BaW oWoKjX,w/lt@t*/p)SPDm;"v2zM< ި{^NsVJdi˰XEiV!f u[WFPHYmx1"xMBB]? iIə8a"EPB՛n;yT&`1Έ3 ң*R{iY.jhKXQO*Q0H!L]u:MöPo"3(;3L4EݭLc_ sߦa`:<bIZ$x@ @ X[ܕ~ 1ι,ˬo  F _rL_[-'@ e&J<{.22 ~ϗEY. 0Q 4EA[rSMyI'e~UMBLtHl1.E#.>ǯ kIa^2 Sh\5iMI‰_`|kB rr"ځ97pY|?yR>K+4XQ$,@ ˀ},Y0GҘY3׺KK$Y$=s4IK"_NX\ajR2*^Z9[HswrFHB NT:(@ @^ 'O["Xx9yៜxf 5Se+㽏W5VrXW+y3p=` @_ 'w Dk~]V.g,8kٟz^2SKdn|8¥S"bBnvf2;;a/Pt_R^{9!gs]Uu |_%kCECۙ;0=Gi/Tb"♣: zmF06md k=E ?!$5 d*ĩXK@ ՐpzDDPR<0$BGdLjs?Ykm/!ܺ6cfnM%&v3\4 !fj=qGj ƈ0;eB7h@ @ ?ְ kd(pcc ^y(z25e*K"sl6N鿳rYeU!3e@ 3;uN3㸬=}V]&ts/{>LU%,緕?|}k/ӽ܆H^N;ξʳm\0|IO*+`"ed4oP !& sayx]WD~,֤u=Rڱ34f`(SEtA(UBT ]fhq.˪z_V' <]EP?V~OS_JyY=>0ͭRi$˫š;l;d-|\g.]i1*&\^_YDL z &\Q%FVo9 zKhb&P˲LyRGn@WUgu6K3]&hєBxpJmeYQ; nN^0=Te3S9Pg[VOa2hA"oO0Ն]Ӭ&ҳ8@ @ ͭ͝mx3G`0PY "֕XîL1SMeUZ ͐+ 0Onn.///>͝><Ҍ@ OGDk*c:.2NDQξ.4FURTeⓠ߾ջ)/:;Wd,5 龆0adAT{ G"ğ2Vb(ct3Ί̍VN ._kNT.xpYW87_j;6a[<7P)gdVfw9hɴV1BC>y\ ܘìgh /ҘAFKKL%Q@r"дED:i˺XY:PdY}F<rk4м ܧh%Ti %B GEpVs 94Ai4@ @ X /E2x<onCdɐBMK`})Rl35$jXL/W3lMU@ x@HtjNI1)gH1Zg|Tޣ̪ŧD)!Q B"c¦|-on/rk̆&ޣSF<K'i~efQ"Q Qg(&>"Rg;kD&;D$k0(VAaC*aj4y2;ѤdPlXe@9113fBd&4RH`"I2xLyy'fɐ^p%7PC4TT Xm)ґrZ!ZƒQ}gIQ@ @ ?Q@C=IdɃͭ F^ܜ_\.bl}33~uF,Ol'39H1 鴬ЌD"%?ɏ'{® cg^n/ϊ&/򇥿1Y/qx_A_ GY([ G$c0!B &PQ&vRzm1gd 7hӜ'ԮYSJC^BG,bu'aSfc %P.2%eRzܾEc`b-8L(P&\U!$eNNUoud{"I+Č@ @ ?!@GZeِ ={˃x= <`TG6wNkcz^߇Q9yBm[t:]VLDiF '7 g/gTtkDp}}32Xꊨhߕ~PO9UF;nf+\Mm4*bΫkV^ b :b˜5lgշشnV4!Lڇ_@R zoN'%&dLhIQ{q>HmEViQvs揼,aA4ݷəGgROY}*}RanfT|HI"ܘ gwk@gF+3B }+d.@ &8z7o^z[ۣ"ϳp+CySQs빳1Óˋl\.aqh}>UׅF~JpۖB6,.m:3vhЬfrĀ*\z?q*!&uJ@ W?Ӌ/vtexHtbw7w8\N m4mkcc<j1WUխ@p>*uj$U6} ]fiY.](/AuEŞJ1zcx9 5-ʴC_R"6-SIꑵcgZ/+]q,iDclqwUؑeE&$D[i*ĉa*ϔ+%6c +f[nz%`!RK Vj 2b3ws2 rp:!V^̧ѝ9wHܚ UqZWjOrn!zlH,-7U[㭭hYV~(@ kW=Œ;cg_nho?ne"Ug~f_n sBJUxΑro$'rB5kKwz;vu-匌#휡1NO]+É2Ɗ2L H%o:5z ްf˹lXlZx4pz 4'U>4M'q^Zb2&A@f$҆ڶh45'u#W>ďFo5M0__[1=Lqa%DFb<á/g1;@ @ VEkG̓ׯ_?< MC;K@yHB>:ISPA?766>O'77UUm{` >]f˙7E*61xJeƤD밷u^U~~6˔Xk^ [OBmߗI6=㮀43jl0'Mӷgh,?1G4~ϟ?Gi^X[JǷ6'Qzp>]ikw pkjq/c+,cx5{5Nc-GH3@ !>ϘY}l"{]d)ݪz_VS$ZU̶-l2񨬎}J=CӮ'tժqff6[Ld՘IUZ4Bgh"U>qujZe쎫] $iiZTi,p~MCk*5k4@ <ˠkʘJCe`6G3!VzliVfg:͌Lp_@-Yh]"jw㍈TWGбYave@ @=Vg{{{/_oo޼l4!ښ[. 3T Őa[%ۀ0TSg(y3PH L0Ɲ]iZ. r@1Sthi+ˌ.qBL%UɱO g/̐tRa?mZ\nh-G͕_.Rh߰׌Y(C?"I>ބ͜cJRz Y2=0fTk2MA0.(ɨbiLsZ.,x}hka+bJ@Ff4k$=y*7Yv4NK0qY]UIbb!Z:ǎLԇT%ӭsݗ4% H:eh72 _X#@ +?y>^ytio>tV!ueO6Z se*wI cI&s.G ;)e]ތ՛a"w8b r\+Z.d(@V1C1{Ud2 $52S#y*dU3(.H euCuOƟZf wcg,EJiIY-cZ~`v (U@fͶ2m ADC8FT= q^eZ~ lԳdi)͵b(-pZ2,ёIqӘ%pe/m ~A1fe ܤMtYc."4~]>V "^+hڥԇ͵ufǹ^c4Y)=3vr]"}9ªNd480:Tc!0 @ @ 5Dkeh4짟~Tyz8fyY&~zFxK\jXW+`֑ M̹BO;[ᙇ .P>)wp,cTІ`MiLv}e!_P]&']`/lTJB<\UV~2nQ"-g^15EVaYbZƲ\ pѹ}t\[dv(C1+)c4hQt}Ji((U7 }(FmZ3t~A|ixl!/> iR i@7~{HK{H TZ榕666 @a:JGc{+]gIM suK2f՞NP 9nBzY W&x_ë~B20oz+g43Z}8[ɰ%@ ȓse߼yׯ_CmLR;[2GrX, 3Ng>G%2fHl2-c&{^׃Ch~+"˲7PI%쵽I`&n1fʲ: Z^LEM/\,Y9ȽzR^yg!]y\C.(ݲ^!AWRo.50Y!ZGe?e>Ue":Db-:IӐ+ ʸgVZEM+c8"*|\X$7&| hZ-Rщrx() Vh2YmA3GU ].Lʗ0H:t##En8lpxӅ3bgp|P Q"ius*2HX] _ +;ǒqJ @ ~Oːׯ͛]9t`QMr\&K{}u=N&b1gL@}ϮLPe8yA^Q`07677㭭-p8̋ jfC"wvvC+DK m ' eꩴ/|xWVD}B5ܨLH~6R;rR UɌvyn7&$+WYote0;60flؙ5A{ o*M0 qԧр))h[_=N3KeV_,tRQ\f!pć fẔ$~PC*{=K4T(ۄw΄1#tc 3N[M0bT.TK#$&@Vٟ߬’.Y3Yp. 1MCMFU\VFQD-"Ÿ:[@KXe>J-9ݣU%BS$&@ {0]fwo eЌC-է2zhq !UZ. 8LnyUȅZF teYF;;߇MY7ǂ Pif{[SSs,KEa@1ÑHa.SdpQ.˪zs2yZ3y3yɪZ2gqO*1A}gˉ(Zt^/rNJ(bo7v`5Tx ]Ҷ_ͪo*J8 a'nZCc3!Q9] ӘDC"2FhrP&Lenh}e`@ ";rz(vRXTYLM^H9"|%|;18)L]!^y>zc@^\%.q.^}\ aH/`8M ҺPpd@ ເSnŋ;;;_Z[ZUUr2\__\\1_j>~$%̳ yqÛhTf+ۢ*s *WeE7'E5@c@o;ȞY;Due"ްUȳA# Т!'Nƌy1;@Yfᇩ(Ƨ+3vW̦P ]y`۠UJ%.=7p"Dh:X/ \k v>4 "+"gp8$- [֦i pgw7[p]s`EDԖ2֬М&97?I'r2͆fH` Bԓ2ëS 7{˲,KG^>BؚAZAw'&I @ 3f4)gϞO3.2s~~~uu5fe| roTT[+tjusssq~>|~sssٳ ޛF"h;l g T iF N ,ȌU2)֬? Er7fNHw§toJ,v|^d޷򿮪җ鏖z ]̎[Όݳ@ٕVlfm _y' q*תnӜ_mJ9CkzZ-ҀLt'hZ՘/ \e(*Q15Jrk!24WC).01tZ`F1M^3&~«4gDWF4oFsZy%@ pOp͛{{{Z%ʷ6Fb2{pvvvss j"zsn02L~yu\.^o877cLp\hcفgbJ3Umۅ4#G3Cc^e6LJH?9LtA?&,scgGQR媊KT*C+aB@ Sx^i5La&HfCm-&nfěo $%+7>q@q|Pn =]Ք˰倧u۔R7cQh5. b&"#IRD.pi$rbmWL %Ci&7+I .iY~3&E̲OO>3N&13ZLL%0jZbO=ʨR}k*Ӱ2QMgy]2}j6U #I$y.ݒ*sNQԅ˱zޭۜɩ Ժ%/*HɫE;Ō5he_xQYPHFNLgC^~Z   r|S8t\/-- <2 eqժjG;;GGGz˩W9ޤT.akkPJ.,.=S6,.vváw$" ϊ &]ƿeRF("i޷ Wj8s&%XjduEdI~ўY7Q\FRk|lF=ϧ'$kVx72 JCb^w-riTW !B˭ʾ4_*\B˜-XHhW gf9&oR ΣiV UL%B_,u sj@%1TN2]'?g74uQ%=b )0Zdԟmz`5j@1clJhctZCڿ<윶9y.q+mCAAAQJqdiaauuuΝ奥BL&Lvrӓ݃z>,PƘw{R-6H ͵Rwl'8gA.[ZZ^G(TͼvfAA='d7ɘџ:8EXC~MʜF.çI6aF ,%0eu f-cCikޞ\=Bel!`Em2`X(k*W-T[ʞ0,}k3Whe+FhciU>XAR=4#MC_zN~@\# %Ҫ+LVb][XnHONmBk*zmVۓ[?IjLJ\I1zV&ju6#/qh &AAA;l6~j*NLq3cklZZp847[mXF.666t"K{,-nAP,7qv/GANW^yH'&}`*yB1H=/J+]52,.51#!JsN<TꦅzjCNѤUc@̮thc1=+#;VPғ2;j.iihgEFjZg/S|M!4!{V+o~?99ZX)E-7+qUG(7r>M&5ƹ4-zB2TzۍhEAA x#=td{P K!_ xMЮXMfr*9)Le_ h.|BAAAqjJ\naaayyyiiT, E)EQәeNO[Vd2o.Sbqum{ MU5?k߁K$=Sպ4͘H9;-A]WC?3 +dJCL2&-IRٲsh^N⎐Q\H]X{zN+>s2>nh#n:S|e hef9[cAI32m!em&4'.*BhŪT4Mzb*CP+ m=IH8N$DXՅ>eO>'up W6:H!إh3%9iEJ ~"/9W&>DU.    N~**JJP("B^WVwww:vl-XͪI(< |=+d2g]ZUzg;% ťVG17(" M]]Od,RiYF+/p;!FRA"ѵ9ڵ (>&Tfr 6QQ4IJiLJVcr,&_[.QeKQ`$2ϖY/ MkIjHHz4Ayqw|')qYb=Ÿ c_|Lʜ.rZgm3NlKw &"VK49DyʦnR>=Μ)umNAAAA^g\?ՔpγJya!͆aHeB F~r|g2]f46[xVAPX]]z`"YZrN%znqcJl4Zff {[JC`WJnu4f=sbVz?e\D,)#S,|i>&5̈7}JRf9+rV`,&Evh$5T'UW(Iȡl9suQ6Sv/͍P&L쀇vAI,eֺYgSNtM" ,̖0DAwHqJ=^It!p'K]eCl\d R2Q"Vĭsc="^k:^4CJԸP& s   rC{kXQ,/KD"AdU6-Jhh4Lݎc3rUxmmmGmܽkey_k1Ii*9S]1 )1 CzVhZPEۄ  V\z8N2 Y=<(Hu6.}yKHsƉj]!hs^JڬHH$h)IÅ(\K,KT#)1+C8z$lB/A=؍Jg8M1ʩ\NWz-BzYC:ygh7)~367˳P?!Q#5 bi|ڪc+EAAA͙0ƒdPXTLحWcr)e߿8<0hgE37XՙfY][-..r9/Y@ͥQães|r"!3R\.m֪n+piAy[PHZ'd%=-s۱ E_zl Z: #^e4*u((E^2N+CǍ @ݖDVhP9Mj=-PF I53w["H̾mRʘe?0)'OsOA/X-Vٷ4dA%I2c^{l@n&[ dtb[50z4g_̨9WZCc D   2d29-HPHӾ[31yVt2a^Bّ7^gW0 ڃdL]^55N^;}46JAʿ`T9O&?M+:h07 KI<Xɛ]٢kxq X㶔Wvy̸,<ހtHtRJO$Ϧ"JPRٗrel;4BuOF,6SO25W-a4a7$#2vA+JbRCAVlN)퍔)ō^4ZDڤ)` ޝZF4o@6 e4!H6CfkQ " $P'B&Vy6$~MAAA&ptP(\.JM f.ER(:i^ HƤqO"WWWonn=|h2KKL*̥3J)jѨjN~&eh}ĥ\8P%+V1+uBAw xc}=l+)Jin%/ēk zi !b |rʫ y:LPf+zIJҔ81,ˌo26 0V+}&90#@Y2MTGKCse9qJ3Nr<: FR5'=Ҥ ټz. |ظc{̘}hd4ąN1P)tOj-@+2Y#HݔMz)fֵNy7I AAAr8o G\\.Jl&ᥪ5&Ftk꩕4~DZLug-|~mm+Y\ZC[-FVy|ttpx5^(Qa"L%!W)oL6kxzvQ ~A]i$-(# dTyΜVYiѵEJYRg˱wS9LќΠ2)Fufs9(| JOȮP-)WF:nG".%I'm1VF b$4=b;a,ݩ5ImBNfy4DA~YaxIJChJA0`7ɑ˨0jڗ% 9 &^Q̺V_ KbQtz%E0}P7    7PT*RcIl>QjNOj׋in<3Ksmmmuɘ2WU[BhԨס;;;PEJi.҂0I@=YN&.J zϹAϬ\Fkf5g+!/ܷ+I6E,Fqb 3sҳP؜SZ{Wk#pр c%>PP©=HTu_M!;,EZm"?G0)KPMe-v_PꎐX>K[wk@ 8b?z )QURJe\Cnaھ}·BLܞ'A#KiƤ$dzYu$]'?4AAASG\T* d2y7u16-pvl6Wm̕s)dL[[]&:w2)4GGG;Z o8K+R9N1ν$33 ["U[  =].j]dXF,NƢ+y\H2ೲϓlI\N(ZKQ.JɻTePhejuOf,r"삙|ε ? Ϳ))N fhMLŞ瑬1#D֯XLi_mc7z|A5F*   rCi0 |Td*9Y̎(ƣQݮjZnF#R"ܬlR\pÇݿB;i|o*VI)a^?88x=rreal\v3okg>9T2*Br|`AA @CB-<&R{m!qUƽRRy%xvnt=Ge$Eiަ^ra.M-UG\fX[|nCO7v]9Ҍ(3 4r\xЖUtJ՗F$b,m!^ _;nll߹S,ap+h'{{ZM1yMn+{kNWGТ1{n_.}Q.;:8̾Ru!{RNS/H0mEM?YA"xK   93fXt:NsEzE,:Nl5AoC,bVgո\.oܽu޽|>^s}f2O>y%czn: ǝvVJ奔hr<79ռh^e!GA ̮s] RȓJHjl$/}Zq:Ft+ztCj,{&1+J)-YMe#?Vzu_ȖP !:R%„kWhREmy >+2fx6@)c$2F7 Mÿ$AQ}"|G?>^ C?`?Cֿo0(Bd2񧟮-U*T>[YY1.gvɀ朂@L&Ihz1L:`%d*kc~sgqncbl/,/,|I퓉>#u7_D/DW̆/{ M:^4P)JCE &R.~3Q(cL#mQ?AAAAhP?Jᜧ-);}Z%ljF1tQo:ӬnB/J-..nll؀'lv.3kU%!\^?::2rǏ\﻽X"zuVt: 5qZr'' N3P @8˛7 AAbe5JQRقo\^#V&S=6ق$#K_ ,ct 㵤<$i]TBcf'$f1i+0W#ʬ4vtg׍]4E?ڎW33V󅅅;YLe\b27Uﻫ»0vCK7xϧ~Oh]e̅ɆmB|zʬzo[ZtAϦY.GsB>*j21Pʢa:+ߝ5sR_Z`d"D[oG\wFGKP,%G<ԅ:e_}3Q3ХҦ7L#5 Nm~4V?ԺySrtW0"C    ?7 f3l6Dߴ:d~*JRR)/,dɘ+2gɘzq|xɓY2&c!3(f[MC m7P%  O1ǔge֭X(TGv{W?e:Bj$6S'uɚG#(!SEJ N,EC,E$l~ؙY8KdL$A(ʋmPՖF\ AM0,J}ÇO~vzd@LH^YVONNN#k҄M F1¹fnU`8m~<4+^]@-R6#AAA#=TF1dd2l+ѠnlAߤdr3Utaf EC> ]4j.Tu8çOnooU^ 1s!lBF.^'S)K_eVAA=S(i> [GJWcyT/~y#\@}N4#fDf)s,4) oXFRÅlYL_Hu/}9 i4#yiZެ1X5 ˎTN(ӑr ,!a5KeqѣG\oW+{1NLL4RʘQ8MM8:6746&DԤ_Z8 ^(YzK(ɧ~G\'c$    76IR e)Q{/l4r:'hAEJf*fS2[ieeN4w !$],uv5 ΅Mv]VIm͞IO"J]V  rgb]RVD!-!k˨kM3;™NyΊILcRl[L[ȡ>єx@+, hHҤu`C" eiv6%D# nKAxryRqEE0(K)g;_(淿]][ꫯ~~{zt=8ƺ0O&(LZ_[zpB%ȹ|ci?w_*p&t*fsv(v]T2 F'nʫ/-/g}qT΁:CӶ>zwo~l4E^ :;;d2[|(c6_wкVW?.}&w76>ztxpR ^7|Yg*J߻?J;x7< XRƆ$ABa_@'@7.7 _?{;}LMEs-{Tx<{WpҲUsFHյLy8Ql\ԛ(fQ*keF%"   ȍ L:Ōѣ\1%5I-x42ˌc! Du0t:PܻwZPp.8T5/U=QJFqxx铧Okjەeܵz I넀>tYZDz9L$G ,IRC|LR{XE.ddc rŹIJ}~'66RgYP2Ia:&sS#-!{Jɡk}{Wf60 M]XX( Q~O\Mns1,^%L" VMk00{mo+43MNLMAAAyqcy9)lKR5n{C.3BsSucBMRteqݻ=2\.}7]2yF#'Or~/l~K2dE`8t:L&wdUzQ̄a2BN+T  [P̸SRJ2τIu|L b#cloW`HH3 > Qf[M!;Rv@MV&ݮzf&MD) Y2}]d7hKӴV,RoeAƯF$8RW%fz3`ᣃÇ0>88ꫯ` #jA8O??/Ovw덆_sx6<|0c׿v(aXݻ#h߾omw:7[[gg'~zgcдny3WWW@+Ln&{G>JK(^:l@tť/⣏?ۃ.>e[DfO>ѣ;w@@OBgYh㧟}un=xWKՂ8&=~o_}UV?>zK4 iZUZޞtsܑ3djv)xGwsNqNW/| :REpVCTƄa쉡?cκd^3T2r_@nBAAA9BHxHvo 4(5nIe2ʝw6yzojڂloC׭]{ ߻VلONZ7bqIqyɲDf f)fxìnj R'e=X(Yjf-e^K2YX Q ei0TF#]za̅ / 5"cL_гaHl&gLvP:tb0 6YsVEf餖4ܻ2_6d"J'n_V||x.O'GGGJJ[B.uS"wwww4d4~1tzT:??q\^^ ( sm2>tmou<-GT 2Cm 8? u}3jB1Ij2V9^01i"}!-ו.վ / JRV$M}Hf+)8ՓRqBBj/M։z   r#T 9 >g^M2GQ4cj2Tjí׍L"fZf;na^?::~ɓ']b0J)9+1Wp8# m&M3Uef}]0 xȺPMaWEʴiȴ+A~i4+NGDQ&v2 #<ϧi887f~k0fօLoXo7+ {?6'h4v)¹*LGGG?=$zwIfPZcܽK2\"ǏCf3&M oMy@ky~ͤA%õDڴVOZ >A-q.za }TpS0ѻLBHMBAAAAʘCW*f!f#9l[zY&&%cJz)].3J)5%crrji֚ ؙOsuE'&=B=`? AA=P@]HhLBG"n5y>_%F;gl1de(5g?}`PҭXK=zYZsr]l1 I%%,SV)dv Adn z^ݿ#bۅ ƨJ޽{dzQ8$ t|k/Wza999:<B=7H'yz'Kb: - n^DkBJ fsoof"wPze;Y71W>n?'v[BDqt@j֐89_@WT՝m|Qf5q7aԘ\.׷pJgi+!ք*1` cm&==E{>3p ѣ5AAAA۩JL13X;O,+I^6ƽ{=zn1%S)5kJ)h4jO?~}oe9r712{\fez &=J# ;V+fIe/,V霞MD30V>8aSYV.--AqƆáKD5p'pyIאj WOll zO7ݢnw:pѕՅ l.ci9\F)m=c~&l*B}6飞S!WܵRV\#pwcsppJ;qzOŲ%P@oަ<#m {ɌdI H>W5_##B#PXj&!&2+ӬmWFӜ7YTK5Һ#dӘȞ11lmPAa<jť_Wt*74hl6[^XM Dq Wi4~}}\nea1lޥZvwc<9>><8sF蟳Sh_~ W_Cj&:;9 n2sl3=u݋';2"A7ӭ?9A-dǠPa|7mކFes98s̈́ޔx c;{޸ dܢEF˜&Un-n7& 4L|U#mxWd𗉴꫟R    /0U)&LWTP#ʯPozt:PlܻKR:a.L2( fytxxdzqv&h4ٱ6g0s݄2ǬG'~Ap3>|ϙnv uZ,"}JKR0, ׫R]hcf=fkw+2602 hbC'(BL5@D :aISȦˑ 8 `DqF>à~mhݻ+kk.-,kkwfY7u^i>sggg0PKL&ݿOʊwz}7'.Ɲ;kkk`\662 ~w^ess/ ðZ¨~ow7G]ZY?c>FDQn?|IRGGGPS@AZԠ?=> ~@WV+V+L߹~׿6 tiKP%wKӟ~hetnwPm{ei >T^u@%կR6TA 27wvkP,WW|2K]z+{4CJyfk|My++5T ((ig9k[( t>54jǟ%G?;q_NdHAv:$AAA ڤNlfRdRٸ{Çj6h4r9+ >y|g}/LRVK ̓`<}iiX,[݆}W? #i4`^k,anWj@]CkZP(;懖Z_׿l~"C ]E%{ǚM|])\W(~'7?K=}:B/27Oaű@Y@@;ۜ~s׿ XO ߇CW?yxaq$>/WVWᓂ΁=b3NH:.$Xkx( XO䭁DK`E/-)RU@_A0I3 hL,   F?O1RTeaݻlnmwr9e1ph42?GG;O>=88V^o&3뾉  [$ϗft!HJY ̘$vU2YLvcUCȃq|۰X6@3 )v%IX_Y?Hl-H4RZh *E%ni]T2 ̃~^8yb2zr~~?zqa_t:R|.'8;=u6$KKK18P(@9peQ;z xrW(*ggp<\Eı̪C``BiV;9≯8`?Ϸ~L…jtaq1H1nj54U\EIy`s&_Y[m ԤhA(jw6bk˜Y m?==?]"$(ڛNajoXr~ppY|=T'Քؚ +zo+ ܅6|X 4 ~Lg2SZ W1 egF1cF#.-wC7񌽛Kke$h].]^}7UA6sO 'zɥ,);1R   .3q5xD2QkFCQsΥۺFnؤbqёɖ 7wN=TJn擂gtZ$uv_㙲*vwvMƞ궥\C>56Z}.gyAϸW櫤JKFjk*M N)5R#?5*0X_b򁑠4o* @y^:@d^|PB5.}N4SAAA-0KBt 8bs\1&Uct߲kϒ1]&Xfɘwvv>uɘ6k%tʫ3=T  ;6zpB.<ټ'?Ʋ6'ԧJHRad8 q-]/"?ce1,+&S@rڴ(2XqKX;8k F0 G#煷Ei7q*x= 0 MFKI](?r˼Cq"r+KLqErXQ‹ѹݺDNN=09gZ8ui V>-^a׫Ida.Z\(DkFX WblateM)RF6IR"bRgsgc-v~{GmK_V8Z   s#F!YO2͏ l2LRw<02r*xWems\z#  ;)f D yFB9IBxb%fs0q[WHu!BByu/RҤԓ-]޴izGd8-rVyT4N cvuXf_"A;5kw٠ܰ\p8t%/d45W=ɵsWuU}rM\5texvp \cWJBޗ$fSUV0#U\8)o)cnlc(pz:T*V.?]&L2k|^BÙ\ɓ']f0B)u۸JBm#vL4~ҖirAAcd4Y9Θ.`7bQ-eGȾIy`BlΥL6cq4k>r2T4Lx͟nv;ȁԍXEܕ/t6A4㯒Lndv V_l ŹKusWyb'f}U^PVR s) V[XςbWh؜hgL XƓCsLҼo"+A̓yȪB[e0sɧfcF qBAAAFI需hj7u}?rWLZ4b%cZ\\ܸwo x`}}ݸˤRƱF1.Z'Ojە6ӛ&c&>:Rh``qSX8Ol;^  ;W(lMPcJ^gtO)|";Ru(f1{ng]Wb E$/b1}I1!͠u&Sʉu"{JՈe[ȑSs ׸vy!~!J->1UeRb`'[x]-,'tO4"^ 0/65Fctj0ĔCsӟKBAAA䦘:R\F!K"^A&ȭ+G]#{>ںQ*S{it4O-6S0m2&\Nd= ޜ:D_,]3  oϣT;~H$H27z #>dpA)cRE_jy&R%yb9>qqۦS k=Rf5-Td2KkHyr7a@ !F "'en ^d< [:*e)`4]/@ZPwõDCD I VcͿaNe7w+FAAA_fC((q<+tD"HAˮ(ȹ˘dL[[[[[6F)uɡ.s霞nooת`$LuvM3%EƖC^iHh`W8f `V:RՅlZ#XX *VEk<m!|MU C+J/Mn.ӌҦc+ݗ&T#Ma@. 2 ((`p %.vf#=}Iy›7 B",U絅n=%iJ̸~QVH42V>($ǩ?U{o-   38hq1TL&4<5lfKĹ,.޻woΝ;r^t%+ZAk]78S7."lS+i$.b1R#]>"eYfrδ/fGLB5hk{ПS>c  gI{ǟM͌ct@O)'BQ$)-2‰OU!BFn1mSmk=3&yHLf+~I5 B1SR:26~JZq2Z   +fh<0Ɵx2 T*C15隫)ppeqqdLDN\? QV_3 jS9ЮT:TA2F.L:P(Fx42]fA3ZskXӜDpnlb :X4c9W&a qI۔et9 >OID3T,O/MERGĬVf/RCG=yڬ)ٗ)dKX ԑ5Q7 AA[(3 >2D1K4 Z"i2g"A`P3`K 2&|x jӍ?!#/JgRaB! AAAAn'Zq<*f|8 㿒H5I*K`EO_ܽ{wΝrJܘdK3P©7RDٕ+m)O"Q z#pHIgK0#!vKw3'%KDn]4 + 4Y,3)4b}P&e-6n7B%2AA[ rR9x2R(`NS:iW @_X4/%*ZԩoUAw #" o b"3 nBAAAb1ǃŌNr^gaNl&t:='SJ2\S1'R띝js!ĕpS eA-r8ID5]P^pI  7΍)ff Ep7Ȕ,he̖b= 6n2cQD4W\/oPBlU&%{ɘ*mV.S.SKrtd1u݋ݝvETJg KXf`Gp>SXͼC  oPIqu^IJSf8KĊi6Vz U[x%ʜ\ʁ#q=ztiliFW|-*}|L=u+OvqB~v%Rdͬql=jB ٵ"G4  5FBHSIyh  x׷23FRLP<4*-z)AIL?Dnbi*H7 AAAAe5qY}؉f.PߩU|r 充f5pb+»tڸܽí;wJR2^.3CJ Հ*=~xwg*7e04Rv0 \m_KeF~^4+13o U~b%*8!)JRc;}jl`\ڃPFȾ=ZB5Z/,'L4BɫK̦mˡσ2O#c0k'G.xv44h2F%C8mu +؊U5 <&P%   `""): f8 t`1ptnHH7R u΂9\>ت[1ć*AIHv#uCF6quZ?   rF <_Wy8lP.뵚TT.֖KƔL$(cWO=cnJ~eNNZx<*v_6/U-Mar9E'll7a],iH]FCڥlLJٗztdvk=MP  ȇS9Cw=Ҽ݄%ğPG{/E!"VI4aGSG6%!Ydzk (Ô5hd    7,2"Zz Jev7cG)f充Bw]d*6Ç7Xw%cLXN|gg'.Sξ[ᜧS)ҒfgrT' Ɯۅ3.;BbAA% Avt!eMP0DRuӾBr1֑L2&~Ru!+MؽೢTFq_SsLŐCib fV\t= JZIiH3.L;nbFA~nf]=c$Wt4ֽ~k0c♁IQN|/2TSֿaH eI/FL3d4i4FdkN(f(uͅV40 AAAAn|o ^n{ݮӋp/=0F2TP(b1u]˜y\62n=x&1].(gggOJ1Xkzs<(AAw+)f&A 2g,H҂oLچTqw=ZRv;a#.swƖ$ccN#q<;c*ZĆ&Ras95)qT 3 dynS2.4JꎔXuŲ@ aA[2Oؔ:4ήa )p쳢M@Lª^8xJ둵փ "Me8at]j, T4BrFimh`eOCm^w)/m^gAIi|ԿGjo'5!   0Nh|H$ ݧN3slX*-T*充p8Sڃ67\faxe\<׻&cvɘMov `R.s48jMlr9c0V*L.s}5Z&S{~~Y2x zT7df0d+BJ|߿t:nfr=zVxl3c{  3f (MRa,h)yf/1&amV(c<!e[ȑ2B\zyΖVO}hN2ڞs;Wy'Y_&jq?e_"!3[_&βԼ k@P7hZǤN0-Z>˰_!AƖB>~;ǿ[ o[1WNB霝q! e)2b\KH<+:0teqѹH)szXL$-ͥqIkJ`]}3<..-V**r3Lk6 oXNS2bAA3XfBB83F7|DH#byp35[S2-mV>5hE&9)uqL.<.l!`%Tq|ՙgbv8+qR/% vK[f,䖐cbe-VAۆĔJ=z?>991p.PV[3Sp{l<^S7g9;;\:U.wNO+=&IiS'u/KMI j]^P%kN$e7M/2gIuTl(xv0{`    o2M3EQ1z錆C!1/f̯JTRa/RbT,$l\fS˚-]fwg']j 7u 13*Xd2gfϞCkn=LC &9zJ qqVLƢݜŸa|e:BrnqF.et- VBv1FdCi$%>_6s[mVS~rYBB9NK>34o0M98]io(u[mQWBr&b rdrss?,.m=@ه'_rCq%U(8ƥϳz.=WϋYgǼc}%.,ɨ711{։ikz4t?Xc3ʭbCbt!&X%IF}DCԾۑ aʹT`mx&H=1RMj !H7?E!75&pId3ARcD    7̼ 4M)A,/NLsx7 Coi^asM2&|ɸl&cz{rh,|RD |>7cx'++j, ă?t8a8.tɭdEnb7oC](BtBP F|bBB& g0ӓ攖MaII_Oӄ1T6Hّ&肨̄mpΆnciLJPs=к'e\Q3 AAA䭡FVT*0}ߥmikOqo1ɜ;zɳdLgg{{{<1۴Zx$.36pbLc,H@onب,.f2|L:fy~ap̴d! Ȼo&Ҍwr61,El MH=Tf&n[ R,ϓΊcq06sT2%x޸ׅMICg$!52eR)&ۥ<kSHкԣ*HP+ O31kw~+w3#Ð>_(x>L~?V?跿-ï߹Nh4ݻa"Qj{=CovghO?_ n_Zf"0R/$MNk 1L3V aJ&0RHkx B%F[3!DW1-!c2RzRob,p<)Ffa5+%j %:ү.A%8wB>ɧnnngggkwp·`; ϟ;L&#yJ}wq~^a`f`xG%o ޷l6[Y\ܸ{ 狋P߿^g+~ <::ke8L[Cp[m+LkB/ANkc<<B?X$2SAAAA%3-"-T*R)5+6Pg, k1JS.s|tjnc0+ܩ,,83es6]Zs8=nsqqqzr򪻼o#  ? t&ٕ=Oߤp^OW'/z$MM!NƢr쳵* 9(*h4h,ecElSf9t.i2fɪ+TGRrLja |.?x~ܪR`i{ݮ C ӧ^/NZL6Pq?0hT*Uy`.LRj\ɓ'q=4pQ8x0PB iqܭFF 99>v{\t$^Q $ePMaf=R$Һ␴󘙺.%8\[!+t1YnoIujchc@b6cF,b".'    nshjՋJe t9.FS5c.c1=}dӓvE+'je˘߿^.ӳ|L/%cr"eFfdh6`Q2 ]+(lLC#}I=D[_y8FR]$;R+t*NP] O\NP@HYFg%R8p0Q[0~Bb*+y] >0 ޿ͳz.Ϥӟ}jEg;;;0'€~z]C*:͓fS$ /Ϣ^x9 ଣhDK$DBٍ20(__++}?P+Y2'K>r#LK#j@Rdeו)us fIK H&Ma-!;BEv11rg^%Bߊ#(FAAA&E߫M⡅r.M&1j3G2NR;ݧOnoo4hM)7(p;V(WV߻ݻwsK/xS-r04 "^u]LɄ  ?/ whadZR6TG3[{'QO*IaRV.ĭU6R͊W%%AZŘDQB*OW|(־ˌP& JrYjR!f#I A79A scït:MAx0qf E8t:GGGz]*rLFI\. w^7PD"d0_}{zB +x41zE~owGw󄱿 vmx̤2i% VKmlS&$r$(av.Hi1/dQ8NefΊ' PXD<=a^ 21Ii>&m7GAY['|1w'3RRj8s;PQ\E|"PRFcR7yJU8Ks4(&T+*Bhƪ#TOʑYz7+cЁ |(yGqҒǏ<~\՜=pΓT"0;rh'K;lQ%B0K/fK"q~ p<(Z4vGpUv"ဣêu?qΝT:tԭq1EN'a zk ]3х@%M]@Q439F&8!ʺSBM f~1_N ^A2g02!dKȱR/c ;Y?8fdE7WNrm8V9=a   Z$f|>N'fɛ^X!De)~zRy$rbI8b_qY.'W1 ĬI("I)#e: ٣څ  cfss!e2xGt|Ǐs~a?t:g ~'|~a&]\ZŽWd2?J~ֺ&aBB ;vѨjn "6PdV*0-cjJu!dBbHP#6(mlilJ&jcwJ՞"{>T1q<41$m=fzq=ߍbo޺AAAAKqX4Ad2bf$eILJri${v}r|wj2mA1Cݻw[[__T|pUɘfRVyvzztxxrrRo4áMhAA~.P>s $2pHXEǮ\z-e7fYeY|J}B#q2{uGPk(fFZK]Q7.Aq:R2J^^v:"NmeV<|0Hť%8f6[\\v(v裏nB!p8_%SnC![[D?4υnn٬[(tBYhԄIPt3yzȆFy#5)e"ڪV79or5q'$Iret~ L6w49DAAAA&٪r04lBdŌz P`٬j8[̄txm$,n(~rz޽;kkkr :a4M2GG'''PMO_A7~$S9`I6GJ7by6-!_vwSFHkt/Dsn۶Buٯ|m"xE{ʂ! 򋙆xnZnݮ1/'I8kl~o9==BnoAɧ..-zǏ۽]$;+ oN$Zm4Qurzvwg[ HBB|tkRѾr$7^e&hF>61${H@\_,Щ+>KBFu`47wt#M1#%UO/2l,5W3   =fUN4SV3!a5(673sVJbz&8LY<; 9}۷EBfe: ORyKRY^^^[7,..ehf~\f>K&uvvvpppxxxqqt㱫-eA`YNP;_gcZ*=z1;,r s ,?7!GtɃ0*x?LAc ỻZ-fr-㣣Fp4j4-pq.26Fp{o`x0gN 6?O{;;PZ0U^oEla3m=mEn2y>)RG!i}qF!.ِc6 ̤QR+ bg`pgSZ;=4|#Ӕ|f뙔6gW'~LQ$    ?NwbR)űKd&7}x7L$|_].90 e4¯^oB$I4+M)XcsFid2UVVV̾R)$I8ΛtiGJvwwwv ^G nT1=J,K>/,1Omwfٙⲑ$N8+%> nĢ.d~YVk5}C_A?CVn6tD"ёK&f{_L!ĸ+ ޺VnR`&ljcʬ(!dJŻgN Bʜ $:3q~JQ<2Hfҿ,Ն 3.`sZiF3ly)JsN0s,   |8/dx2; HRd}x@n^-Y<˭z@QRj[~~EgW;:Ad2|\^^ZZZ^^]]]T06TBZzxtwxxXr# qc+5e9K0JL(>0$1 ]^J3f$X,W '."uQFJwbY%DٌS F 2f2$00b4c'vll.07 δ5;a05Bn|L^ g7ֺeQS #!18JX.7S&((== T\(܂J F3ԎE$B9k{96a@I})'[\Ɗ   qS@nvh` 03F1sQv Pt:R7zְ~?c!ܮ-=qm-sfb zrBPXT JRTJg2DUzk#T?\\\_wl>FAA~^nc&drXȴ<sII#!5ʘJ>,1]vbsZR#шE[T#!P\@A痎݋qUk5x~~vDZop_>i iyvc.4P1> **9|%(< ͼ乢D/L1`y 2{-!!l2~~" ޅC]mwJKژ AAAA93̚0ΩU.x͛າ(J}K" JwinkYؙH3͸r̥n:kLX*-,,,.-Ab1뮆0{DBpu.ssvvtb A(f :pjք ŢVP mT g a5Pd6 ')Avas]$W4?ҥ 5u=M1%Sߔgl66;2LP04iL>8D_)2wO%N1ɉ,ƁbT~A%AAAUMVqm%fb֡a"1?Cu$NI$T*JI\\^ZZj&;S7 FC.E~ ǝ 0L?[Z:Is|P(J|>8KY[ }SNNNZh4R'(Apd8] 'm>& bj8e$%%΋>2aH]ڦPXj$⹵( #AM4 f(1!EF}ڦj #wOD5TL@'*e `vv tK_ yCE1)IUr8'o^d 3   s09BVEťI2/J 9j60$:MP=JfWsgvñ疚ɦK r6Ġ7UMfIZ,B!^CȚc\ IG^Ǜ+oeʜm$Րm(=Ўe5-aFs2Bmo+OGZɿ)sɭ~Be *bAAAA~&,i&vŭy*H5T,KW9X8`fh2.S\&~l/7{~U<[2OdLjevv`0 v^ T1 g˾cN*"'hѧnRN,e1)m0q \WS `č&l !}3|:4mQ3    oqzxMm43% B2 |L^.xs&4NYe#K&aM4z2+։ll(L&{vOO Y叩ݮj;{GGGf[Ux(AA)fƞct=Ą_M_)=CkEJ㙎T-!JE<] P(xB鶐x  $$YhY!d]wįhNK5VZ}>yzc/rc4h6ry=)O#y8Bc97Pe,MŘv d?uȕ (;CAAAm"SFQӊTMf48 R6sh$M/8;Ngpּb&EAY5霟흞4!(_<AAOQ̸K(|5 \30D`Gٖ'dGTpR (a,ԈEP/  i>BJʜ.,iջk c0ӵgz J6 +PhoPku|v'׌lɂ uq\e/ i;dJo4EϴIo   r˙9XZF.3-`y4Z(әL*ӹ M~ oP[ܻ*o2p{V*Ya:;;?88<88??W\AAn3q*ϬIDC?`bÅfK݈e5]b܆Η^Y@'F<} F;vJIƤ8>⳼㙴^GX6TAKʸ|1e2NjEۅXBAvIP`$II>2ɾjjHzW˞R#i.W|t獵E$ma rӕin17YM7K+@ r;qzgʌ :T˪EIBd3 q7IO^nJ}]i=Rz$͟MԚpcGf ]7*) p}*62.bьel{pn2D]{R]_/Rl>##   HRRJxBq< ڝN^2l2`$Mn\~+a {|q\xFZ====>::>>'fBCT.4 rT̂ Qd9[B9G(Owe==FJaɀP\&mWY<Ҏq"i\@y^ ۱^_Tm{ PVly͸t6(T'ߣz%N};R֭%E AܒS2#K *-9G8dʛM ge.yNB*cR(2 șfGzHq v>\Х9>٢H#m W AAAA 7A)uPZ.u:~Jr>O$d} IfinpN><<<99jNg4qY)&cBA3`$,s4X1ҟkxbI3V![Ra sEŀ\Nn_rxX)E?>!y/c!ۉ5HֳKu)H]PRJ76&_ hܸP67Pe9d olG*W*fHiW+05|J?7ů=o=ssFo^JSR 3(f-$ov<}.RSh•}EʛTgcq<B8ܥ@}brZ (ZC7"MlbAAAAn3YFڷDQ4:vX\\T*bP(0 }.R14U_. RҕY+l߳X=gD3Zsjń>)FE 3!ϓ̛L\fc)M`&='1pnK&Lz9&BAAAcvı(5 VZ,..,---..L&L|ypܖ#yUr˯2=IeVT٬j秧xnp8BLA Wp=1#f$ BeZr,pub8ɲ ![HB+,9u+ݴYBI)F x JUP;[̷whb!F.&r4.~[wzkט{^"7mk y.dKX_rjD$M '󕓞SD/3^2)Z_773կҙL41&ݕ/n?am}p~LՀA8{&ϲd,s3OJZ<! LRj(`̙d` #be ?;Yn|kNBUcc0=S2,ާQbxy40"3ѳ2m̓H$ R}:|ՕRv[*a\^Z^>== ӏ*ff\svzC6y?/r4F u <ǧQ68Db fh7Y>}BଆpoNlڬZ7 eAAAbeEh4VT* R b1[˙tq1'ɞr6]+,,>A G~&29Nhw:^M)9" {,fMS401$JYLP؛hh^,#tûŠ_Y"_̟ #&aDŽ  t 0Yd*pO[L=aV:FXվ?{r wDdU(zƶeZ3YZw~̸l-ɒ%YCY zdD}#"pxH!HQ ,dfEeFob-96t}}^o|»OxU,&*s|ZEyHa4 teWlj*2)kg~*MrUQL-7?Ykhne0+'ʧl~jL/90]Tz*iJu'I.WNiUP*B; sr&%lj_BRJ40NO766677w.@1 MS8gs],?T1iIK1|>*nJF&?tr|}=NOhÄ23~oCk.붖.EQ.Bw}׿|6[=J;WX~_+iq\]Nzٶmk]v%Ucg;akZj'eQnڋ3S|W)׿TMqyyܷl`^noR(˔_UMX+|ɶc'7"I}y~LGHhb",{;;+Y[[_XXne^h̲Lw,c[2|[,l6L&7?7Y۶M .Mǵ)!UbI2-k6{n;n '-e8{uO%)W_̎Ǎ˫e"LR)ܖe>d+YhϻԫXv8kDK9!ٗe|\j1rReԤ۞VKÓYԩh{Wu&U:3{6EꞓwoOs.:1I3^o8 =熖kRTO/^_%u$rS% x}Ou%{xu)dt>I߰>BgSn?xVASu_]|5etuiҐi>_[-'s} R2 I&rKr"P1zUdѓsl Ht(&Uҡ-QC?՞gmzƫZq{~+@S;.忣濲ɷ??? 1vL&o~_\\\ 3<Ѯ{1YE9i2֤vWٰdF:QLJG>:wœN&f޸J7[B8>:w?|3c)S-S':*6I"+zWWR9 j [+]ՙ6I).bޔd 3\s.]jEp ͲSuyA\!rdenV/=I Z|zD;Oz~Õdג_j<o.w,SMh8~;JB/..{$fhj=5\E9 ߐ'$}fGy;AK_)dNC7IfIw^68ky:G=EJf^s6~k9X'-lqvuh o6ҕ{|)=/|^1U "5Z]ȧޘ]o4nۗiuL Wg }wcfIC4+קP`åV2nXZ"%Od޼ɪMV_Sf9?;_7!.13͞>}:LLT&#oݲTR&qHzʋqRߔ̶ڽJ>;D}ʈBLF鍃 $Ng'?ϮJI~!sETWZ<ͧj1TtW?!5μSgj\ǹo}їtwFfn3χchny 6m*)yvw㜊^Eٱүc7X˔֙rULdԾ13r*HQKEIEeެf*`|"u/"?nܓmtQĄWlZ鑷{~XyqM- %'Zg0udeuJM3/4W.;Z2ש4{4_AU|6{˶0oAD.^[+Li.yc/gDﰜ==~ iiɤk}.9?I)M&/^>ԉlOSE6WU&I~NUlBHJ(^Gyj&>~Uax7Vg1IwsDY68kw*=o?A|gIB:i.ak ,^tS+H/-z5UT$Iwx7R ߁ya̛cuܬ$\&G zZ=Cy}ǃUPӵd%T+%[d̗qGUE$Ƙ5ݬPpu9uPzӲSU^>^lV*9KJl|ƣWS|\?ar/E~=ns%ϤF^J 2'=]ouf'mHʄ/ E23zpWKP֟۷(ڔo>!*m8kzWރER29Ky/1cJtCr̷%Aqe2FYMRc)EZ)SޭM{?X[3?vӾH9NٗiRI͝/U,8I:%cTMV{qc:*Oc_i=plzo2kLwa4tuk䨍kQpݭ1ky7g)>[*2,mf" k۫Ñ{ LwufߗoY]M7INC=^y5Hx6[4nX2]딞.IsUnV}zed_;+[.jh2*o ͥ룚8UJgQ*_k]g__ZQ&_?\_Eb&ˮ@ٽULDLtL9lIIf˖LkN|yI-(3tbZ [V3C3D[[[{ϒ%iENoZs:Yvm NB vK&u}-7)=ASMW (W15'=GiU!|Utu8N۸:gK- v&n!r cFI/C ZPOjQSi|~5Ew0srr")̋>p||쬍1vfnlp}ٮfj|ˎ\_r"j]Mo7k#!S].ϒOdfR`ySˉR.o8Gѿ??}7l:5u"{~˿pc#$ݐjb&O$^@]Eq}.ODߤMaoA)z"Yp%}3bV%d.Č֫G7л*.ӪW~2 )e GOzw'~véULJo&}3bqX}e#bmhۋs+g~*i65/T[Qjfl!:lw~ߗZμ[F13Ʌ |\w4#pq\5㘞ᴍT>}祘eouRIK 3A yzؘ=̮F":# d -&zwZ9#1[3⑎LK3Oؐc5`2:D nm"5\]햙H_yگ&;}7߬R5_Jͼy~}.EQ(}9d™ |JGfF^fTJ7ׁĘ'_T:zYQtt"/2=cO ]']0L JwRK3[d_]L2iw4ڏ@QZnZs=S=Yۜ8=x?v`j|!-P_>TYg~amw9 ib5e/{Ԭz9s}>R7"OHTju7Γ\z9b+ADK"uϥ]Nɨz"\*$Q)w$$5֙)67IbVii)e3&.`Tsp҆i}397]ސD;x;tV'c.<]ijk.srb$mtvh֐3(ѤḂ:nk쉞F'BRNl-7 5Q~*`P% g}bJ3&sݷ>Ϳ"xIX?Bnc􅺆_Mjnæ|uV_ !.^y.M]_z9-6N$QYsI7J!YhJl099 g2[\ ̬״ն1U='ܲ%""y\]nymY]T _~3R qy.[kݞ {ܸCvY2?^tƛ$ʻts_/NB䬍2}sIpoҪ~ n9uOg!MEc k%.ûq*6R};XHVSzW_5WՔR1/=z~ kKQe;̆ɧ+ -[+mDENJEFw&fooC6y/K452j"y>)I.?o9fL[675K6qS0C3MrSxI]EHAhqˎZ/{$ʳ6ָ .L<gjvR2fxh9FVT2H+;YkAoK~Ii>OӔR*Y-BWyק69vo;njTIzEV˼x&ôc~J- mzZ=W5^'۠XجV2osc[[[=4SJD3- mX,]b5EGؘgtR>v\Jk)Mz(?ur8tܫfR]FJcnI7u=v֐\X}0wQS<.J<<]tFiZ?a{x|7Aͮ$^P8~g?ַ2ιg?}{?ۿwTdѶW/~ٳvtt15K;YK@sKݴӲ$ӔcjEզc( ]J/nr2Rxf$ʛ6fW׏?ν0//.O~oϿϺ|4׿_8Ư.pc* ޫ񠼗,-3Q͎gQ^sD~sJmNr5ĉ0͘'p4鴍w/tѡ7XXEX'5ӤnZ~ÓҺO1M<~?_}[ߺ/..Ҫs.'fyox4fϧi4ȜZdR7$G!]YҬ&V̶mf~rda~9s\uQ2i~5|yP$N۝z<?ۻj1e[s>?ˋ˫_|;׋U>1;q-#~>Mncs%sC[֖2ί籋DTxNk9˘<İN..{q[r!\~L'!Exrm^dBx7CjZbRZ2X˟˿/نPa [X,NONenbWWW/p~?ۿ !\]^UOc6Ӿ;f_f"gѻ[byziTZr%Sٴ B,M31/ S y^ϟ䷿m]b&Otٳ^g=???ӿ7y _u>E^XK'MzyX2o7x}J_LHg>8VGfiKI ˃U\&)C|ƧpRy |DJbfQ8{Mg̮w]<ɨc'E?&Lkh`ϻϼRNF_쇖{;Wm۴ץ*c?GyiCj8v]\\gi4_Nӿ/?|{,o[OEb?]5Ӫ^te**j^\M^]K3W 5dpj-2K2FuV4ZnGc|b1t?OMm<___̯?{o~ӿO~|6{a/Uբ?WTL:- Dz#Z;=-[U:1󐞵$I*3NEJ!uei4^L$c"-1v:zH-S_5%'d2!Ǐ+zM_/™,v&J_?c 9h\9%ҷ_}5*dEGm:)<ӢĆ2W1/鮲-=wCk2~gTL4f o[+oR[4aڱ|x2wPu5ٗ l6]=1`=x]w=yj ҏI/\$Yt3祓Gf2D\`zV:ٚiʴ+?Sk̈r)R^ƌZ^~1bS/t:Ɔw~L]EmW(7Nu˾n)EeAZ4Zd$.r!҉Ucڲ-g$ʳE8jT%P\/x`^wDkv+KVJm0zTOV)|א,|3]t^X;~kv>qQCf}gV캈rRwN<י*6W]f͌5-3mEN&_UJIҨ~'Ii9&ٙ~?],</ɽ= Y)^Nͻࡥ}qVff̪܅'k'9Z]&[z!ݽOZ۶)h4:;=N'K8f>_,^ɓ'sqqqtt4Nђad?vc*[Iz(}1'rL%d*HOf&IokkKQ|49M4贄ljdZ>__ cQ󳳳]ɍgmml>zCO?Ǽ_oLzfn2?vq:wY^$9~LT p^e˱"d#.ݖXKbaru-Dc)-h. ̘ҕҴjbv Ryg;WWW*'m?Gm[&* G?~g777}+3y]v ZLbޫ4qAB}~9i^n*4?+huL2Mz(H˿GpmVހ%czd6,)=MWTu>GώFחWW!SJ''4^0g?kwʁzfӒm{֮J˚N)1ӡ{5BB]76M-!q}ФfQ#_-n4]CyCy4ؚ'Y,OxcbJ??][[w%^syy'O/kwCjϩ81j:T22ȿ[,]$S+2iZJHw]ܫ~ Om;{IrԆ6> }o=?)仸}RiycryuӀy-'&G?OE}H!\׺fsskoowgoEۢCӶ+l@Y,޽7fck-KjΣ4N5}*9\uH+isS? > hzaK^idbC>k|қ?Z#kx6nRKv0/( 'Wqt>W-!Y԰N׼zr~WˣgϮ'HDPfAjZ>t6Oή#S4f,rd"jD=lk|[`&OtMSR3z*%%,5._MČ+c˾t/:ɻ(L_~/O?C^7h6M&'|W gztGdjj2qLpۋPô퓞vnُɘIb%TxhwrW\,^E< FRC_YflEdӳ Aɡ^NyM_>4i8t/l7鄔R.o&W+77]O&?L&H<ϔрJW|3<ǵv"+a}~|"ޓ1^]]W-'|^b4fKDG?'?ǝtjj4_~^YJ3ܫ=n_%{$&}mZeO8v{0uT&)=[g'a讑/D/R iYW/Y:qQ;DCWb(mm륝 uUd"WQ/SE$ ]" x\,Ns4ܫe42P/ٰ%p?GtrrWD~?tXF6q̀Cgx䍪$1je {-:'UsIQQLy@` uVdRk%sS6~>ͻ%JDd(.5f&O>|>>>áT7y/89:ڐ ;oYI}_'6tMv$4|2H̐1}4vrwO q] T~:>35+hTV_1cqHɸz]MRsJ5YFZQhy|XF@&$ϊR(=2{<V"XZ}K<ָ~X_Q],2GϞMt[?c) -_hsHVQw:תNm<7$ ?z%1Uy{zwx;_xyC`KrQZ-%(ҲFԷVd"_j"Vb('Nzc4,ӠDxæ@'mZR2J]n }ߕ0-{rVٗi˜iS V-68qHczY^e@fn#s)T+ܔNFoQ]װΫSJ _ 3n4Io_Z޾ggNe_'>A;Qx5]c8YwQ.6x։i/Z/W07]_/U//N8΁r%Ælz۴z$e,%@6CyakqU?&sถ|(nj cqܫ#\ h\{L*x5cЦ5[*\H􊍼7 uf݆~җ$!z/V)G/xJbr.KYR`,IOQB/GR"S^ɦ;5WEK6,JrF@+En\PoiQx$ KD;yYuՆ_H:)V|Իi'[撥* *y;OfjW&ڰʓ.Uט\ P9)X3#pR-ǺO{iEhe#.ºǍ{Ըcܢ1xR}>*ݯV =jL߲5f+M4GH"m zZs-چ4Ⱦ։lLlΣ\&E~LL-wA^bNߘ2%ڲ6]<-1$W"Γ0Q(Oʢt/ʓ-i{"J}(/dyjbnh=j (x5g:I㶜"ht'!.D>Ms /Oh٤JkqմQ&ILDoesM˗{`.`J:6ctvז=4{ukGyEudt&MVMyMC<+'xCn8Wf"mxƛZ\f>MI QôXkKQeGVV]!]g:Ĕr;n!cy-3@ks$G!]DMwtٗ*I ZFyZ5T[܈ΐ^uu= ӊ9 c\uo ѧmLFxҞi@J$ƈh4N2rgPJOgڴzIT bЧ飥f`鑷ޮK9$2 PyÝæʶeW%d#%.'F0wuGaM?Q'%|Sk̘'ZQaj3#MS>1Qq0)*[|Li}A?~`K>Y6NcN%R`kjՒc\8ʮmk,凶DӶX3 s>>]\tK orԖԝ4iǖ4˫\&ɓ&r&tט2{6`kj] f |ܮ~LjIB:k")Yfm4IlYI*4DkLteC3f٤Ɍ̒^Xs[ʐGǧVٲ:t%dR< Ϗh3;e*]Uң(Iڗ]criz QMR3zҦX ݩ[זK8֓<{xZ.92N'!J'5Hrigcp>TL'mR4lqK4Ji&}f[Tuu2辳ᒌ!k1 lXJi|Z}@kD{w:EvōNc: i.÷nW1uF$窯9lJqArAf9T)a.1#"cA!ac#5p{WhUCNw,ɬ?f䣐Ri"-標mC&prK;aczDѮgQ.DYoo|h1{4vk?R`TvFDjzZĥD9iU|h-e2ޟ7X'QwJ[ON)iɄ|B Qs$ ZInLϨ5_%,({{CɛƌE!;yˏ^%4C=um'Σ(Ar.bv,?vY\/(W5rCjmǏk%딏u厐SJRKS ّg8j֘m6O۵;RWhB8XZLwydDͥҌK;%~1wf`m -dqzD˲5K9qqϓ2Bh61uh:M煗7{ƣ6LbQ]4~mx@cPn֙=)_t׮1Ҝ̾ʶ1ZKRqSK msfS *Ie_;']Zɾ5M]K]{2f@kls0d(X?weVo>B,%$2 xwԆhN@RjEc ^[az5֔6 1A21]KRZ4el-% k#Mk!NyMiЅ(UJӤpqɬ[ڵ1|85G]g|`iD&ϨO h)0KGe%Im[tRqdZz I*Y)%zPj*5客> 烙ИDԘ1M?vdjHL_#+l֡Ԣ#cs2g^ح̰V%>727I)$yt".knwIq"<[qzKE82o[,pL^+E8]7)-缫EBқ6硟'm̂4ɤ x{^ hܫP/ ފ 𵩊1ӤGR>nk\GOco쀴gY[ T٥ X$5m~(ǁ};Ζd5cMrLg8Ni!̲=5}K\ ЊB:G!X})ѕCYbd)''mkMb2:6Aޖa|"4'qF7٬:3/90ﶽ5 jq9 ,yi>|gw;=|ԮIh^)Y ԳPIP\^e%oJ"е0$xp6Ҫuh\åsJuK6t$I1D|C\tnvݬ! 1- 6lŘ4L|m]_JŔdRdqIzƣe#^m-q൐oT4NR:p~u.GR mit^HPǡ{UW$&]b3-6-_=\Hyfi7nlj 7~L6vKV꺓ϴm jԵ*OxY8USsW=nh-&JQ4<F!Ʒ엤5^]>p dJڷP)mtJXRS*?W[6O 3G]N܈E&Qk6g֬ۮOt, 71m<}o- jm݉"QLW)-J4iZ/k:׫%9餍Ӥ/Yc]GTL>.H]K!^/ -8ݠ擨l.B:mj3<6mdLV>RkDky* * 'I.YOI[mo$]/5],j6ZYڱv6Ǹu+6rZtdtnmܠflfKIXO'[1tVjJP!(G0 )Y%/8tW(G"jRzYHA;~Uf|+ͤ:WLj Zx&oEBAb>R;&y{mlm7xjد[ލSf{fn82 iǧ}owIȚZoJg_w89`}|34BΙvH~ %tNژ;kɘ)o5y|D6D5FSriZւ$fCA][cbJ1:FҞkapvǥ oKRs`s#vEޜ[%a nXrv+Q[ݤUSD./11zF5ofSUJs$1zSȒD-g7l \'Y!{ndL2ft.qENݫ]ϥSmUx9$fF1!4eH51tܔb-԰d̻^tƓɐZ^S)O&E[J\Z8qN AzK=ލb:=nZnTJR\=2惻j$2$>kup ,T ~u_KS~R1!L3=^XK$~sM) N>5L =2kzZsz#2$f> H`e,QqmpZ6iڱR.*LB&ɂU,%LN9Z8ή@,aPJq:tuO#⏹̠el>(?&UHw&Q&Zڰ];V>jg5$_GR S&cy37veJȔD)KB$!=i\[XGc-NMbN<<)}4 7rӶƛRd|y%SS"qdvcjYԖJQGkə㮋Ӳ1,.λK֙XZb4vBAZijG%M 1aPR)Mv;xݕ$%09 86&MA^ictҞmyv%=RjuPqZǴmVs!V pDc_O]?|Ϭ GmuVmc5~4< R)M'Ҽ$fR@_73QZ)sJ+FzVsᚖcZv=gG!b$oIj Vʃ^te?[ w kmά= "ƼJ-E KfK)Ϻ= iYy^i3zqCk53)x$[ m%yޤrgYRu: JwB1Mn ˾Vd_ֈL[n:~RFOӽH5{ȁۥNR:^B􂼀5fRШ(J[|Of`iK|jyD%.eN޴֯F;g7Qw6Nkb& |$fe1VsIzme&rj+g{!\'EJKff"!=jz[*eZ<5# <(ɘH z4=v*#1ԫ)mgKZ7I*ⴒZ{2AgT(&M[>hJe4pRwƑ[2?k܎}dYf"5}/n#ƌ%GڊSZ6î AbL(tJ»'k:`Ҧ;{e,!Vs#4㘦"G!9Ӕec Y AxeRYdXљ$πi}];&5'xSܽ;lAz&NYRBʮ0i3QJ=![XKH5K=bSARc0^< $faM]Tiqrn;cvde,:{IHg1NbOԚ)Tʜ\ƴS_yPgnKQH!u;s..:JoyTyIeW4\:ZS>AJ\F 2}|p!1-Iץzo;)fJ $.]92Ĺ{0O-AXKsq+ovmK rRy:P@_.Wcc;hJ9itX*\-(7o $fJ $љ8EH̎I$[4NLg2WIfIĨ}pa_:IS\4v{eoHX6:<ƫ VJ3S)0qCgWZ26]EW_<ўOu5RfL!^khdf |޳[ e&h[|.cO@b>!fd2z3a6\ɣX*[UH!q"=Y%1\*Y_: _s3 dGDO.cI0ʦ]J͒0+b^̾H-9a{u|u5ku~Cok2l5{4I[WhtQbF* ~>&]9|4{̾;H=[Bb>E]#֘M2M2 Q;SnLPh#bmb&bѡ-7̪eNkX*+t6ic" qQ`Z_BJlKyڰHI$7͏T2|䖳k-Q7l;qi8S*ҦǙ7pȻ]햲nt)ȝ+杳iISrHiҳG1-D+OYwƓ"ɢyzKHc&i$!1̑R.#(!{;ޮ3]}1xL4-9sp)\4?9;< D4e<4i[p#7nvJ2Y/oK}^+,ͣ:ju~u<'Z*SP e-c`?24@b`673W ܈Gyݓ.UUJ"fap(ƳPʽE.}3Zir[k\e1tMJ8l}R;+Y)bL%.C8زWh{K4C6_S'2"u_Mk\4KE3hAbKD2&"שqQK&O,knfק !bR#>Mi*:4̎cGJRXe+}4["O9oaϻo.p-[&ї7ka.KL!w{uNSjɘZ27)M@YueJAPatDlk{$W@bET˨,DIFweKdhͣ n;"˘*vodjLs%242nQ2V=fhif˧1]R'oͤp̠9n`.DWk=jʼn]t:]q/U%5eKRe&'ZZ"53l)\tP~" nG1M\$YC}fKR=ҧIC̷$ڧvS:PQt.!?}o7ݰK{"R}c:iDJ"ߕ~LK(&QNk;pW?ZlrQ-:IeQL~*t[핚:UvIGkT"QsMu+ˇ; &|Z]1͒\Ĵ1S86t(Ȭ2 kg$Оwj?>S)SA18{Igm UYm5y[?k|TJ E<VyQzmͪG^!? դNK!IrʳLY,K&H0Ԡ1$fHןhڦ44ryfܦe&jOӺMG6-u*}KL<#jɂ$i;vݑL/CCq)O#R$fO3ⶔKb'rиGmy#rkEHG!4 y-oLHTN$=o8w3nTa]F{Ԧ똆U\4321W<\K=vGJ?pJwK Q1CKZKFCѼծnE'I!P`S וP4#owKieÒiؖ&M^NC:mu* .z7jTJIӮ{7(K$y>'am(JqB^ '0Ҡ*?RDL!m813uT1=y/JϬR'ZuZVKMf>YH|-fIi$w\k쮳2i^lcauӕ*/Xr3㔢j'M36I:/M&,^G9m㮷빩yRr';Urm< iSZUA֛i_G=_ZI=ɗ2nTISRgqk~γ6_ͅ -Q&v~)Cy T$duU.j (x[H܋GQ5"שjݞkIϨvҖ}'Gm8 79`A RQNˏz\.miYC?uoYyQ4)3]'jlּN5- ,CW|jda6VGd̍ȼիhF!>Mq_)L ׭[Z> t^kt;M{ Tb.fdQs'ήQJChv#FYur(Ơ_tpف[D/bxaٯ2; ՛8%S$ $f%dt&f84nϻNFW)oJ/!te.PI}k$I=" dd̸&fzD6_w~wF5HY &lZ)-}Vwqc5On"(jNC J->%XoQ=!QZީZuM[G*ÈgrTHIu!2KCC%&#PcQ4eL硄fvp-!Ӱ0o0zeEH }ω9Kfhy߻-\W}̼ybI+%ߓ~B8] RI-hob7Rmͳf_/wI1 oXQ&2MZ!T"yPHQt.Ҋ0$fi|-MTd̤Qw;n:nJ&b6-gw\tJT[ }".5qb֘kim3+ɓ2PE))fz];nkj6-7_䡖H,[3ՖL*3nXϩJo(5ժNEg|%$f8h1f.JNtӞsn{ %vdirEHTr!(&J37q6ݲf? 2$ۍ?J%8-9^*<\j-qx(Ih*yɧ2֘oC lvkU <򛔂E!JX>ho?.хQZIӤw;ve&c>ӶcB< MIuh l9Vɏҭ>MPNjMy*9_:jn5o~ǻC8k$IfװIjUyc(mޟ=q| rJBb]OZ-$v~V&y.at˔r#77^+1 BJ\"]ҚD9}kM) J]6LjS%m9>pYNCR7옒,ML(yq>׫d%8${DgS ;bzl&fgaOOD2-[Fkd59=ݨ4oSS Ƥ+yKsC-+1[i\1/'>K,ČԌthj9'30mdnҧ)vq([q^qRF{ͱ6J*.bJ(9'/yƤLuQc51b m[ZT,$fo vyL]f0fn̍Ѿ1YulKlz ^\%i;]EiȬ;f^cC< 4j{W' CWkHyqZs%C{K$WQZ=Ϗ+I}ZG&iM˽G,-f)ߙTMp5\~͆m^_iBb17MYKqӨ0fj$Eۖ=kɚx}5uKՋ$IIos(l̚]uCbjt(6MS)/Bvq GJOU'Q6ݤ$yRrF5fIqn2diҼAmJz8gIިLײJONsyd]߰-S37"B)Rȱ^/X,$Fb r׳)ж2ߌ.)2mU'I/lb3[7ZUœ2 l2o]RȺ}K{R2kiH7Ij±sc (g1HKSB5D2Dn08Mxf" qhmj7f'1}ldj:ޘjV-q/䧟eJyJoi1{&^_@b[4M]Iy,6xٍ4ϲϣb jv,\^fY`f,Ԅm9wv=:U.E;YWoUcҩeԼ=[F792ɪn%9r$A*#љi03$f.2tgʾ,j\feQt&RUڦ18o@tJ_qҹjT5: A2I hƐ_{ \ƙV$f..m[xjSԪ^$1R^ -mX5kIx zK 5k/tӼ&W}>5n,x@Ot_ZgrGKb6xLhكz38$fUcl[*,J1 $q(JwBPB6t]IשIlZޱܳZ=Y[l1]z5FzjfZ9 rҡskd 2{Mk%4MH|x D65L4b[.'ZvPҧɜE jݱii`K#5gTmeEԱM<[(?nCeɘ88&\TI%ac7f:K2:4i!1QUI)\G0WeLhgxYE%TWViX޵f["k%Je]W2=&oy`j<}N#UC>P|h+oO84GUY+;|vVϴ$RI̬3ݣ,N Hɲl…_+WZsn7o *TilX7ّ{1>"rVDVRT SU\G]M)jyXLzFGɴΌ$bhdcxgI ɘ9B$Ǫ3H!!p²_aLdR6eMg6X{Mz+;?r<Ex! 1Zꫯ|痿۱Lw_+TAle4.~g͜rVٰ2:UQ>l^Ꮬ8fkrݝN&S<-Ӛ1V,q:ź5]W |XgD&M"*\U8ɕЩ?O}M8˂cG,Z5γE:x53cFV<=2vYf9$flp_xO?d0y~ҥFV*RuVJݓLX8e>t|bס38I'']/D{ʲW{4l:,c&Yu1AGtҪ)ӱӺKUG'TAElj/dq"Um:`¯pV1ӣRI,Cz-5C+4Rk'KʨXx8c =Z$f/$fj^7_&vmmmuu5?:: I );2c9(A>Q|J褵!j~LK¤0.ݝOXXiNGt:[|oEQ ?HuZ쥚]5`TNƆpuK%~b.kc-%\lu:ꌭyL,N;m[FXFS i'%xb&,)LE$7R3)7իD3Ò'K9N 7p 1`,׻++~o޼Y,ƘÃZY.T)Th3326Te; ]\.N6ifi˚=ތ+iia(ḭf4<ϳ$' )2WjUaFKΕw>t&)7sɯ<,JƐI)3gx!ؐgWVynwәR2'Iөj_Eի/_riv>/m]sp#F3L-BŒaO2+ OjW_}he___0 Ãlf(# 4g9pstmls6AdH/F';wele7W %Ro4߸Q rrNKvd2y+ҥK\yW|YZ`ot_IiS|w_l7W\y76Z_{^__/d91eNp8$î?U vS(l6L˥gJaϣ#\ٔ$*RLֽ;h?5ʲJ3+粰r/wjps΍wz |趚6d̘e&]KіՑ؛걳j17bf~ ]K풚?bz.b@(Bh{Lպi(;tq !1f,_,hgg'\[[{to8<߽9ZԅZ#kZy_}aY, ]Pi!nm=FGӄLu2o IUxNifsњBҹv絫owoo~͋y#)R2f̧5S9Y[Srͽ8%FZtkRJ\9԰1y}ՒrfM3( ):tC[AX ̟T'']Xc.dž sDkmr3UtfXf;o^^K޽36Z&3Ko|}QrRglv޽btdjz=cCxE}Jŗ*4@ 3'&9]1jz7m̻8k.mfl,G5Mc4(oZ1Te %+Aqy,\E*$SʴOUgF.QtU(q^-ُXƯMj5E y>t|X٣Cг''O]iz=9SHix HٺpÅ9s>7xM\dK.]8޺ystt"wn~y=ړqF8( s?{ok;<<§A\.wvveQj 5 {+믿6Zl:(:((g}f~߼?E/_}p\,Nw}+WZ`:Φx<N燾|FG6Vj5f.p2M:F=G2b>g8; zլu * _/ʛr~_21, 1 g"I{3o\L/3&tC{%_[p_ [tΝU#Rn^mτbg!2aA6 6qC2|;O#!HJelO^kNg׏eqtguzU&>\N&5i 넅aO9-k^٭®R BVat }/N闢15c 4IoX5[rMJ]燱s֤ 9@btexTDKI\^f6|ZwyQܚ ;;=JNV+lZ1dRg#n鏼?T DDN4s#*W>]_Ù/?ySur2Ĝ\hMf5NT)qzn͊&,5"QsRMCK Y_TxFT V̮X3"rlF*/RmlXB5a0;)xNl*O**#%!Z?rouwzN Ļj~yi31KxainMs$5ixEVD,t3:>(>Ejڌ(ǻqlf8Re!~ ? 1f# H[=CC^&aܜO]Q0"E#kL)"<6";5eޡIT=k'RҏXUMKS)rx/l?qкʱ2+˒㘴PXsn"֬ظ*vh"eD+F^;dgLF)yjuk62jɵ2̊QMwxOõ;$fGiJ )yx(#nB1XFw2e9tQ)?˯x]RjagҏʲSWxNdRӯXuf)2YėZx3 ,5d)-fij^!}s31dӱuIR W'^V{Nhp]Co >BhAbS#avf:Ȋ֪5H&,B6u lDTe5 ݓ~L? : +TW}\D雿USRY*Z^fP\N뮗Titz/Yp~XL хdqX7b&2*n_8pzpzVJSI)>MO 3 ~L1ӞD2Q^5T+"K?NaKB$Zj)ΒWeURe!$.EE.XCWM9+/~2jLX%!etMkhh3 "pzG{7اrB2.6ƧچO^H<丳MRJTV.6rgmrՖ!:^(c/{}3jԴR+,9fJTU`f!c>aӚ8PNTUs11:WN=yšIӜe)cukc=ZeJu-IFj.V4J@by4zӚukJĆDqaU7m?pR^z3г𤐘RR+;+Hc8u~=JTXx+ԑ W0k3?$?$aJ}Zbg.2ILZLX*HTr‹R<Znk(}T)S$ӻey/g]o&ܸ)˲.܌KNd{^Χ;lҤ]X1!1eЅܬYcP)^c6.a̪9ʧ~L }BD?,ӌ(KzPxeZKĮL>֘i5R|R三yo4Ɍ}ѫ1:C+tIJ.g4I;tTlF=LNUZī#Ͻ,ésݣtyqK}燩"FZ:O}fPu3?FHVn6lҒBV6u,lJ,{-}}m+W[d1ӳ4QZ;X,ʲ|N55Mu"5"  u,<Df3%^EE}*%V:t~#ʨ% MK6M˄=޸oNכfGGG'{pwI ﭵo-7დm10s8K/tnݼi3i7)pNc1U-'IʉC$̱+ zJ0 \xdMkz&^`I]] وz) /1<_{ne-.(Tصmn8b OYT*fӚ&U͐XSDb?&/+nAִL]tG]{ﵚ͝{d(}*ih:FoefRK|,03ފjLג2n_z~n}5sUrtݿfW^yG~h\xŋa^{{[o]Yɲ,l6v~8իW[MQnjZjzxo ;Y.Qs:5Rmz]S(D3MYJ,hꟾ!"G^l uswIT_{f =/X $ ȫ%%Ɗ #MUtt۩&M?3?*QzҦ5-餤ʡO~LUY& K'Y 'ͭ oO/򊵶~X8/Rav^zhܽsO?=::z~aIX~_yx׿`08_^|Vܽ;Ƙ|}x<oW*W gP2׹6.X(uH籖tDay5]XR& >0۹YU&6ϒpPaxý+JD,ver*s+Mj/\"٧|6z7ߴ6o+[ۗ/_ven7ί_ K/e)~i/$T8hY4u5C6i b'&vH5zΏ9fY‰!,هyZ?idu,ՓxuR|N{ k 1ckiffZ&UYQ^Ȕyx,TOf֬.N<5Y+߮|6Vw߽pʲ'9Zͦӛ7o޾}{:m1)ĢV؃^A)"1Vgy>ܺUz*7 coƻ '|{8#Jtʤur1?^b)dx747q)w2$p#hPDŽs1mj=<񞖩ڌy'6)K?G̮[jLFtQ^Y層(C=̇G.eA+ni˚IyXs?׳+ʲλHjZι^ߟL&,$h4jh4sΠg0lvŢiCfb'fi:x?Nb'1l~ƔEQ\v-o֫?!n: |tl$D፤33"q3,1F3ROp;)7S 8Y 7eժ6Ū?Db&)rX/DIQlN)5a)Ks]QȐ MRzyau4xq 1#-bVSEk`9p[?iQch+3If!2;[޸1*nSee.]zݹ<Ͽ>`9Xh6zX8N˥vcsʫ7z؃Ig++n8z(l~-*vXՄ(S~gg'6^zuskjm9f]xm,j2]Aǐ*шr+h s+T,_1T%jj&2tWKݝ*"7SU9t~vf6j2iR<+ f~G 3?H-uVb=HJ9NG1?`*dsҏ&e?ejwN G shXtWV}?1genz52_*/}vg}aʕ+W^ݽko&}ν{/rVL&(jƘV t \&b\VfAad%lj WZ5 e'U<ŧvHK{EMe~RךpYR-[[NBNʘy&qӳ/$fjUpJ1M헱3?* &1R AKy0HQ2VްETi#/;a+%.r6)˲n{fkd6 $,̲x"zy|7Vkm}O?lnn^txi4׾GwN=^3N˗j?` /6VVWo޼y&)6dQu# )jhmŅ;>A)GO?ufX|ڵݝdS%y?IlEĕe׻hLgӻw~7]?L::zJd1a[2M=/|IRd _K,6㇎ƇnMDŽ[[JUjJ:80/ӵ.9S- T8%QrjMT$Tf_DB3AbxhK[RC;%eNC6aMk9Q\R9>t~l1ׯeyttT5?,˚FX~oV-˰; o>䓻w;b G9>믗p8 V _}U|f/bf9&B3*EVVkzpy8%c*c @x "u7,mfqR*#i:N@V-Z )`֛0x񈭎-*S˄Zt(,:E:IEIn?]r7]ݷrQUS9wqRAm[&Ւ ^/(6 J'$NԜSQsC2\}eubX*\fFT('kt2eٜOҲVMaY0:S?Z[jٮC8kT3V՚ Y1dT #8QC%c&JzS[Rdx/c>AݻeQܽs(,\Q;Q) ˿c'*%9€(*6xNfJ!RU=MG^lZZ4SKÍ_p5{>a)5q',KQ[4)Y.j,w cH<+UK1.ӳǍHr)%8.ΪRHV^,0s%x>t~RW_뿲,&jy2ZtLpl!$ekX9H"j9833@S4M4껄WB;Gg$Ʉ#f]x+8ءIVU)݂s g:Tz՚5{\.%X J\h*6 kjLre26g Zro!u9+Z-GÕ椺J5 RX~3)5}$G62jPJf*0L!BOTVVR)4 cH<,*Ӫgck(E#SryG^R6n: ٨ǻ%OC!'%RUkQ?au*k ];"#HM뎡Z:9I%y|"?Ϫ'䒗 o7V6,5M,>)mj"zBTOT RiiR#EnpSnuH<}Rc O) ÒcYVaYՖ!S-T2fsn?mIw>DM,Wie)UJ ,YϹOin)5e?6i43C8Rf.ZYRqWya-xr2NE+z 1IkhRjjsyV!,jܴq\+5q/y圍RLfItaLGƸHaЦG,oZ3[v-%ƧE4I0X=b3Za=ϊtUk HoYrpXh9pz OھNgt˾dz$m$2[~*1QJ5xZ)R-YLE0t͓MX>OP(٧$[3ZoXnM$,9(cY31 btPG`Kk13Y3sܟz;byѪ&iJHu8x)!J;9˲Zbh m!*`_^u|X&vRל9MT' pVC߾5(bh+3=k4 ^Ԅ#Nh$1d3I)yޏ[s5&I]SPh)2Nɘ&鶉y8-ZzYq3wJ^RTx2mfj/q3ge{NMY >o?'fȅQ/Pu ǭkfQff^)E?p1?o ߕ Uf1!1?DN+y>qbəjc ’sXtlClT`&cuCNjox810^t!7+&μMVΈ+4q%| RÝa^VM Ҝ~`2K]zT(#WU􊥾% ΰL|vY'>ɳ\"#w ] ʩǺgS/˂QEi <*)ݴbLT!NLbeuI[fQ~+UӺgi#TS}G>czuUКu-X?p<qX1N$cqpXmұQb/2gYX9z`>[* B#R[ld{4ŌZP3] /MawG9Mm°4W 03gKh;ehS#c@l6ܣxF8'#Xnjǚ(jb*6q)13t<|r3Tj[[/_+cŝ۷~}:.^w?8pezNT T􆥮tJq3ƁӟKj+2p~0粼ѪNNǍv? Z'w5L(d:WMDK.^lZ1#t"xx]i4/_ڥp{/[.FT=^,UEF!]1RٱU5 QS'ʌ,Y}svh})hsT#2?HH-dUKkcuJ_os*~f"VI)L^7\zi e㣣g~(? *1&Ѻ55:2e~PX"n(c:y ,SH)aT$'©45(j&+5K]Jr%s~/l6ôw8eǔt:{7NƓU k2MxUN׊H5k 5̬&:u5JOn)5&8- ~gx|:SjU%D5[~T^֙(+/]#v o܎gnsu\[__.z]?O5.O5Kt6)K?J 1Р6,mfi`x{󓷵9XcF7%3^jZkkEa@TZzv{tttƓI1ٹ<˺+=^jgRVa0Vӻ%Nbja/rnW eNx(c&lTQ0CϾj%ƏK7!$f౤Z^Ŧ -U>OMc q*]D$"~'}X.wB䝛Nh]ӟ8<#)˜p;Q*L㱒RSF;Q]ɡ)&.egcuo=𴆿 v In ('Fef=6$yBb_U^Fz-m#WVq}]'n͔e꽓LὟN7oޜ*UҊHѐ>y\dZw^1O;+||~t ӳ&Ogj:s;rWWO,1jU^RQ繢I߹}޽{anPx(VL%:Rp+qx1 jZYZ!DB?#dsnd:̊p,EKT3hˉS\z`i7Oՙ+ ˌIf2ZC3WGQNq =SKMJTtJO=33p69?|5ތL$cŋ#T;>xRbprmCmdTLUcՙ+ճ1Ѝ]uG{ʩ9Wкuvr[U Q:={bFD'Xxs+̨1 :<]u]a s-Jyt1cV+f&5MUՉ黝ν0s{r('=2p 1R}NJ њ4HF:O+J]xrͣd1/_|wsUx?g~wQCQ'?T J#/.EۺV=CkB6'jyԏIyeR}IWqL5a< DJ9yo_&MYn\iGA l1O9nJ9e$fU] Ԋ5re+矔֍fsk+0W͘aZyESU/v9ˁE3jFFyʄ<:¥tʓ'}f,USɋɲZceZ\.#͛K>V Q1~L3aiTØO9?tfh$towO>>Y씩㏭J}F 09ɓ]fo/;؃Y9RKy |jz)/Uka|{~\Ge]/y0~bP)O,b!EvUkDUé0J3CrVCi3*l·^(:фTDDY̊(q1/E6tti9,Ӏ!DZ[7?vwvS@)5Tesy?*{xn!UYfFT͘^Ti8c1ҡuZf;,i}0}xyZfVIc| Sы8xiDF~?&f2YyR1KŬ[q?aʾ̘3alWLĐMcz-Æ1du6Ai11~-E20HK0&#5l0}LsWaTZ1RU~ˇgoC\1$f&A{/s YCS_| @<T{?.m,/uR{¿踫3T2KlX1dpǻ? kn T!؏) o ٜ7Oy&ver+|)vk:NZ2/Xy%/J\}2s.LR4լj?1%1u4ef4=^'Q* wH@$'*u,Q}]J& w%HD+HR3/,- u_b@)htt5ע|T%K9qi? k8 M]F&L/ c5)FJÒZHn Si˷&Z0O3caC1-/j]c('_Xe rW}*5S_5提_;FwJ>9:Dd}0^_2bu {wEY9˨X*4\V%ǸVUVC2E،i8lZUZH͓R(^dx99硍0մ|vW_ǃ~_D&t:ٙMR52\6?(ˑF^^H18>Diѱ(Hx.FDXeEL+RLեhnw ?}J%+bi c/|jDD⫯pޛg1zvfVˆ͘ŎwSm"6L=B?~ɻ_#̀bnEyRW&-:,ln_h tIz7E5L&'f*^#ϴta/dmQt_z/ۗ"GO<W ZƬes)K 4̕J_Ѻeͤ!S%yT; eQ\~SΎ/_uܼqZuݻ񘙟]q F5r:1ә[eJܴ6'Wu9p~3/"S5.Nx)9iMk5i&t*0|zf;<?p!1(%֥8(܊cHz^^99؇uZa"`5c>rv2a),|孛7|Ν뻻@UfoL&֘eQ,su{`ÿ}XX !9y̦N"+u]2 M˄HŴRz-,qʺ!43-sҵ kiȈ^1tf"𒏻aM3%}o苰K<@gz(1jbg&gqR`ֲX*N yq~Ө<|魜iDy0﹘ y*ͪs5IUNb $N6*Z8S/Ώ;yQ&U0چt`*D31ND)nUV] Qݤۓi/ogP4e\ 8r?L <S <uP9uuNz3Kr@'=s-EWtV{ d_&f;YbO%m7Y5A "UȴnR,d.9 RdʲdIft[]Z^cL*RZ㟜<3vpKteSxjag9.L|LfOs)J3oxtJ,fj14cG5mJRs )!̊!sUiTkZMjc?&?N^3a@Ϊ8za4L"U UiYJ yvCoTj.6UƉGH=<;rF}f"1nZznbG~!S2Mhk%Fä+D;? q( 5> TqjT'mӀ5a3*f,ta&j5&RadvijՄwn&d2 :i\Uf iWihmR&<#ǚf^iW:QufZm3;<_K HcQ#Ƿ@tjciҺT^}eEQ%78j4ow_y%2ᄅ3bl2Goիy9(ooݾ=͞V'4nMʾw͕b߮sI]5Kִ Q2]?/\\&tl61{R ilM%-骁W奜4Fn"t:o .csaAw^ )LyX8_&ݻw9ucF(eybiH^)1yMGwtوilÐպczfrΣ/ HTb4-e]cbW?WjV7I7 մS#^==Se[[oKg>ʲYk/|^ azkW^?O>L&&vN/z=e_'KO*s\CU/dgH3[^6?cČV:dL!( q/(XOESً׏I! JYQL MU{Rdq`98ν0kW:6RDtvX-, W_VVV^}0LgpxΝgIK})72Ӥ9+=:N]iIc[EA9ێUSY ex\̬grR1w-fR78?}bP10UevUzc1ׯ,˲k׮z+W <.0CzR/9Hbk!gg_R&M7 8QC;.6/dMb!"p.55jGIFؿH#S_~٧L'3~mj, W_}Ga<Ͽq{ޯk凇23M[6ViˣiM~x}Xd䗓 ~oB\ $f{H\+4krsmY:r\23g1b1Y\H}4jS:b."9Oo^F.0#:κ4Ozx134(^f4#:_ 1:0ˤSɱ3tQjc(ҋ,8gb&"L?$S=L3W_k$ϲYVthR0}>ntx)b%-k[R,T(XZ2 <UCuJMTV]KSuDԣ#qMwSf\N'3j90a K+&T Tκpa@6ixR!2|̿A0L/ʩ؏i¬bmOn5Q Qp)=>Y,ɤ7'CIZq2o:ye(imp11# V뎡572o3K5|3R]n0֬lfY\.gYY?b4tlXS?)Sٗmkfi;3+lE+(դyU#S:ICL>LGK:]Ll/UJ R /Mxkq}|'UfL/cR:%ay23+L:+3i鉾)9Z"Pn'c667~ߴZ(TJ ,˃d'&5g^~^޽l[2R]735Ƥ}Sx:?MA3״L.fc?̇."O!cfJ̄W2/XHUp"3晗RԋS'_x͍ \5igb8Ѥneԃ,v}ʕ7zl^~}oww\Yu2:օzӱ67X~n-ʽ/3-U4Fzڭ̮&S7wUKZb-#2 .vڝ~{_ o|:6i6W\qe;xr_ܼysXГ7fBx1cNrw2Ly3s8u{z'9"nrygNL\Iɱ'W!bnRldT c*1 ϲKݕ_Vdwo2;sXj/^x4!޻zhtgݽ{,3'|xHkACZեOp~ %FUC2jMM"#{.\/Eы:tBmCj0Eј,9I#LBĩrIh4}^R/ba‡l_ >'G鳺8UwA,1+L(<P3 Th;3H~%_}YRձDMM]6Te輯'|g|#2)1'r_,@X؍͍;w˲afR6t\E얾|q&iTU`RzׄaL=] =X&֘ƘLI*?B Wanܽ?ÕN(h4;i… n/>$j0'R8Z({ iV&XB\$fFkĚ-,DU`rb=sUcMũ~RŐMO=~LMipfi (je84}xXI~<߽sڵk11KI]ɪ ߽{//o~[cm}UnaJ-YMW3C ~ OU+#QyCbI*"ݶ:u|j2̴~f/яrJ-=QlDDZZ핾 TlXĥ(:-lsRiC4oL'wo\,~{ww23xbgff'[s9Ja[֤֬Qjyte xZwbq0H˘-:Ngq%R*hrsX1̬[UUntlgs^ɱ>c\ kZUTA+ J,>UyQJ5R!jI9a&lL5f¼r/vI};Vfrqz5_|aO_A0<10p#x+Q nQA@`=$f{pC1P_Ë=xY-%&m"'i]bQS-s)j$,0M62c7:!`o,y^;8 )^Wo(Y,Nl_㍢,srNC3\!ڴ %^燞YٗpJ+33&?I6LeTJVP,/E!20TCHǒeceeFV=tyjTIO/>~}ɓX:tlZ|r'ɘRdW§aV: :iUX?e4JͽL˔aZ6YX.5⋎ٚOI)|߳;)X"II)bgSXX%O8U ^t)%P|>oz8~?<\.;3v!4踦~>.EBfë8mp誐MXjlj %.jxnezג_AR\Zmm}= 39LWfV~X >naE(||ejRLR!b!8co,QCQw wwYONz%Q3tөW} Bf = j@T}ϼd::oIc&FD?ntXM:K+;QSᣒL9Vj9y^؇ 뷳wK]mCzsW(I=keQTly0/׆/; H gt!9lTL}E HW-6cN!K ׎/0iS'j)+,g)#F, )*'ǩЋJ'W_-˃2Ak2|Y]jDZ$ExxpPᰪ\d/LiUKeL=5 Sw)2vt~@?&;zM'"G>IlA4'hQSb`;?qT.Hr7c^ ~4*הFUani 3p%~*3jg1MlD$Z-dejR#ϩeYnMϚFl@Kd&1Z:pUЁGLju UM8DUVOc n0c&pg 3bK#ik mJm)Gh2*'SӪFz#{)Ԋ%dU Eo榗Zbꆨ6UC ]CItx\>tb\aX2s&c/?,&q_R[.ӉL={(UI'˧^(Ty105f <-✛%J QΣ֬1*xYTb/NٗǺ/ifzִH)=!ke!]u:q<Ǚj-K51;b@G!*z*$Səe/8i cp&g$%Y9 1cdE <1a.ʒ|̄RɦVZDLۖVKԤF%R/s̬jRPx}9Vj 5Wr"2{6R9,nFY pmUU9kbt⽠j$]+M3WQ$1UOJlpx9P`^HFVSҴ+N-JM [Ua&-bfLF\^T:IENDB`hyprwm-hyprtoolkit-71515e8/flake.lock000066400000000000000000000101661513450241200176440ustar00rootroot00000000000000{ "nodes": { "aquamarine": { "inputs": { "hyprutils": [ "hyprutils" ], "hyprwayland-scanner": [ "hyprwayland-scanner" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1764370710, "narHash": "sha256-7iZklFmziy6Vn5ZFy9mvTSuFopp3kJNuPxL5QAvtmFQ=", "owner": "hyprwm", "repo": "aquamarine", "rev": "561ae7fbe1ca15dfd908262ec815bf21a13eef63", "type": "github" }, "original": { "owner": "hyprwm", "repo": "aquamarine", "type": "github" } }, "hyprgraphics": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1766946335, "narHash": "sha256-MRD+Jr2bY11MzNDfenENhiK6pvN+nHygxdHoHbZ1HtE=", "owner": "hyprwm", "repo": "hyprgraphics", "rev": "4af02a3925b454deb1c36603843da528b67ded6c", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprgraphics", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1758927902, "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", "owner": "hyprwm", "repo": "hyprlang", "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1764962281, "narHash": "sha256-rGbEMhTTyTzw4iyz45lch5kXseqnqcEpmrHdy+zHsfo=", "owner": "hyprwm", "repo": "hyprutils", "rev": "fe686486ac867a1a24f99c753bb40ffed338e4b0", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1755184602, "narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1762363567, "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", "owner": "NixOS", "repo": "nixpkgs", "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "aquamarine": "aquamarine", "hyprgraphics": "hyprgraphics", "hyprlang": "hyprlang", "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs", "systems": "systems" } }, "systems": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "owner": "nix-systems", "repo": "default-linux", "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default-linux", "type": "github" } } }, "root": "root", "version": 7 } hyprwm-hyprtoolkit-71515e8/flake.nix000066400000000000000000000043501513450241200175100ustar00rootroot00000000000000{ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; aquamarine = { url = "github:hyprwm/aquamarine"; inputs = { nixpkgs.follows = "nixpkgs"; systems.follows = "systems"; hyprutils.follows = "hyprutils"; hyprwayland-scanner.follows = "hyprwayland-scanner"; }; }; hyprutils = { url = "github:hyprwm/hyprutils"; inputs = { nixpkgs.follows = "nixpkgs"; systems.follows = "systems"; }; }; hyprlang = { url = "github:hyprwm/hyprlang"; inputs = { hyprutils.follows = "hyprutils"; nixpkgs.follows = "nixpkgs"; systems.follows = "systems"; }; }; hyprgraphics = { url = "github:hyprwm/hyprgraphics"; inputs = { hyprutils.follows = "hyprutils"; nixpkgs.follows = "nixpkgs"; systems.follows = "systems"; }; }; hyprwayland-scanner = { url = "github:hyprwm/hyprwayland-scanner"; inputs = { nixpkgs.follows = "nixpkgs"; systems.follows = "systems"; }; }; }; outputs = inputs @ { self, nixpkgs, systems, ... }: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem ( system: import nixpkgs { localSystem.system = system; overlays = with self.overlays; [ hyprtoolkit ]; } ); in { overlays = import ./nix/overlays.nix {inherit self lib inputs;}; packages = eachSystem (system: { default = self.packages.${system}.hyprtoolkit; inherit (pkgsFor.${system}) hyprtoolkit hyprtoolkit-with-tests; }); devShells = eachSystem (system: { default = pkgsFor.${system}.mkShell.override { inherit (self.packages.${system}.default) stdenv; } { name = "hyprtoolkit-shell"; hardeningDisable = ["fortify"]; inputsFrom = [pkgsFor.${system}.hyprtoolkit]; packages = [pkgsFor.${system}.clang-tools]; }; }); checks = eachSystem (system: self.packages.${system}); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } hyprwm-hyprtoolkit-71515e8/hyprtoolkit.pc.in000066400000000000000000000003771513450241200212340ustar00rootroot00000000000000prefix=@PREFIX@ includedir=@INCLUDE@ libdir=@LIBDIR@ Name: hyprtoolkit URL: https://github.com/hyprwm/hyprtoolkit Description: A modern C++ Wayland-native GUI toolkit Version: @HYPRTOOLKIT_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lhyprtoolkit hyprwm-hyprtoolkit-71515e8/include/000077500000000000000000000000001513450241200173275ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/000077500000000000000000000000001513450241200217175ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/000077500000000000000000000000001513450241200226475ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/Backend.hpp000066400000000000000000000071521513450241200247140ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include "LogTypes.hpp" #include "SessionLock.hpp" #include "../palette/Palette.hpp" #include "CoreMacros.hpp" namespace Hyprtoolkit { class IWindow; class IOutput; class CTimer; class ISystemIconFactory; struct SWindowCreationData; class IBackend { public: virtual ~IBackend(); using LogFn = std::function; struct SBackendCreationData { explicit SBackendCreationData(); Hyprutils::Memory::CSharedPointer pLogConnection; }; /* Create a backend. There can only be one backend per process: In case of another create(), it will fail. */ static Hyprutils::Memory::CSharedPointer create(); static Hyprutils::Memory::CSharedPointer createWithData(const SBackendCreationData& data); /* Destroy the backend. Backend will be destroyed once: - All refs YOU hold are dead - You call this fn */ virtual void destroy() = 0; virtual void setLogFn(LogFn&& fn) = 0; /* These are non-owning. */ virtual void addFd(int fd, std::function&& callback) = 0; virtual void removeFd(int fd) = 0; /* Get the system icon factory object, from which you can lookup icons. */ virtual Hyprutils::Memory::CSharedPointer systemIcons() = 0; /* Add a timer func. This will return a pointer, but the pointer doesn't need to be kept. */ virtual Hyprutils::Memory::CAtomicSharedPointer addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false) = 0; /* Add an idle func. This fn will be executed as soon as possible, but after every pending event */ virtual void addIdle(const std::function& fn) = 0; /* Enter the loop. */ virtual void enterLoop() = 0; virtual Hyprutils::Memory::CSharedPointer getPalette() = 0; /* Get currently registered outputs. Make sure you register the `removed` event to get rid of your reference once the output is removed. */ virtual std::vector> getOutputs() = 0; /* Create and lock the graphical session. It is required to call this before HT_WINDOW_LOCK_SURFACE can be used. */ virtual std::expected, eSessionLockError> aquireSessionLock() = 0; struct { /* Get notified when a new output was added. */ Hyprutils::Signal::CSignalT> outputAdded; } m_events; protected: IBackend(); }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/CoreMacros.hpp000066400000000000000000000001011513450241200254050ustar00rootroot00000000000000#pragma once #ifndef HT_HIDDEN #define HT_HIDDEN private #endif hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/Input.hpp000066400000000000000000000017531513450241200244650ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit::Input { enum eMouseButton : uint8_t { MOUSE_BUTTON_UNKNOWN, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE, }; enum eAxisAxis : uint8_t { AXIS_AXIS_HORIZONTAL, AXIS_AXIS_VERTICAL, }; enum eKeyboardModifier : uint8_t { HT_MODIFIER_SHIFT = (1 << 0), HT_MODIFIER_CAPS = (1 << 1), HT_MODIFIER_CTRL = (1 << 2), HT_MODIFIER_ALT = (1 << 3), HT_MODIFIER_MOD2 = (1 << 4), HT_MODIFIER_MOD3 = (1 << 5), HT_MODIFIER_META = (1 << 6), HT_MODIFIER_MOD5 = (1 << 7), HT_MODIFIER_CTRL_SHIFT = HT_MODIFIER_CTRL | HT_MODIFIER_SHIFT, }; struct SKeyboardKeyEvent { uint32_t xkbKeysym = 0; bool down = true; bool repeat = false; std::string utf8 = ""; uint32_t modMask = 0; // eKeyboardModifier }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/LogTypes.hpp000066400000000000000000000003311513450241200251230ustar00rootroot00000000000000#pragma once #include namespace Hyprtoolkit { enum eLogLevel : uint8_t { HT_LOG_TRACE = 0, HT_LOG_DEBUG, HT_LOG_WARNING, HT_LOG_ERROR, HT_LOG_CRITICAL, }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/Output.hpp000066400000000000000000000007621513450241200246650ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit { class IOutput { public: virtual ~IOutput() = default; virtual uint32_t handle() = 0; virtual std::string port() = 0; virtual std::string desc() = 0; virtual uint32_t fps() = 0; struct { /* output removed */ Hyprutils::Signal::CSignalT<> removed; } m_events; }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/SessionLock.hpp000066400000000000000000000010761513450241200256200ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit { enum eSessionLockError : uint8_t { LOCK_ERROR_PLATFORM_UNINITIALIZED, LOCK_ERROR_DENIED, }; class ISessionLockState { public: virtual ~ISessionLockState() = default; virtual void unlock() = 0; struct { /* signals that we don't need to unlock anymore. It makes sense to exit upon recieving this */ Hyprutils::Signal::CSignalT<> finished; } m_events; }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/core/Timer.hpp000066400000000000000000000022541513450241200244430ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprtoolkit { class CTimer { public: CTimer(std::chrono::steady_clock::duration timeout, std::function self, void* data)> cb_, void* data_, bool force); void cancel(); bool passed(); bool canForceUpdate(); void updateTimeout(std::chrono::steady_clock::duration timeout); float leftMs(); bool cancelled(); void call(Hyprutils::Memory::CAtomicSharedPointer self); private: std::function self, void* data)> m_cb; void* m_data = nullptr; std::chrono::steady_clock::time_point m_expires; bool m_wasCancelled = false; bool m_allowForceUpdate = false; }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/000077500000000000000000000000001513450241200233505ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Button.hpp000066400000000000000000000062411513450241200253370ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include "../types/FontTypes.hpp" #include #include #include namespace Hyprtoolkit { struct SButtonImpl; class CButtonElement; struct SButtonData; class CButtonBuilder { public: ~CButtonBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer label(std::string&&); Hyprutils::Memory::CSharedPointer noBorder(bool); Hyprutils::Memory::CSharedPointer noBg(bool); Hyprutils::Memory::CSharedPointer alignText(eFontAlignment); Hyprutils::Memory::CSharedPointer fontFamily(std::string&&); Hyprutils::Memory::CSharedPointer fontSize(CFontSize&&); Hyprutils::Memory::CSharedPointer onMainClick(std::function)>&&); Hyprutils::Memory::CSharedPointer onRightClick(std::function)>&&); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CButtonBuilder() = default; friend class CButtonElement; }; class CButtonElement : public IElement { public: virtual ~CButtonElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: CButtonElement(const SButtonData& data); static Hyprutils::Memory::CSharedPointer create(const SButtonData& data); void replaceData(const SButtonData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CButtonBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Checkbox.hpp000066400000000000000000000054241513450241200256140ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include #include #include namespace Hyprtoolkit { struct SCheckboxImpl; struct SCheckboxData; class CCheckboxElement; class CCheckboxBuilder { public: ~CCheckboxBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer onToggled(std::function, bool)>&&); Hyprutils::Memory::CSharedPointer toggled(bool); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CCheckboxBuilder() = default; friend class CCheckboxElement; }; class CCheckboxElement : public IElement { public: virtual ~CCheckboxElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); bool state(); void setState(bool state); private: CCheckboxElement(const SCheckboxData& data); static Hyprutils::Memory::CSharedPointer create(const SCheckboxData& data); void replaceData(const SCheckboxData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CCheckboxBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/ColumnLayout.hpp000066400000000000000000000045231513450241200265200ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" namespace Hyprtoolkit { struct SColumnLayoutData; struct SColumnLayoutImpl; class CColumnLayoutElement; class CColumnLayoutBuilder { public: ~CColumnLayoutBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer gap(size_t gap); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CColumnLayoutBuilder() = default; friend class CColumnLayoutElement; }; class CColumnLayoutElement : public IElement { public: virtual ~CColumnLayoutElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: CColumnLayoutElement(const SColumnLayoutData& data); static Hyprutils::Memory::CSharedPointer create(const SColumnLayoutData& data); void replaceData(const SColumnLayoutData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); Hyprutils::Math::Vector2D childSize(Hyprutils::Memory::CSharedPointer child); Hyprutils::Memory::CUniquePointer m_impl; friend class CColumnLayoutBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Combobox.hpp000066400000000000000000000065761513450241200256470ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include #include #include #include namespace Hyprtoolkit { struct SComboboxImpl; struct SComboboxData; class CComboboxElement; class CComboboxBuilder { public: ~CComboboxBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer items(std::vector&&); Hyprutils::Memory::CSharedPointer currentItem(size_t); Hyprutils::Memory::CSharedPointer onChanged(std::function, size_t)>&&); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CComboboxBuilder() = default; friend class CComboboxElement; }; class CComboboxElement : public IElement { public: virtual ~CComboboxElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); size_t current(); void setCurrent(size_t current); private: CComboboxElement(const SComboboxData& data); static Hyprutils::Memory::CSharedPointer create(const SComboboxData& data); void replaceData(const SComboboxData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool positioningDependsOnChild(); void setSelection(size_t idx); void init(); void updateLabel(const std::string& str); void openDropdown(); void closeDropdown(); Hyprutils::Memory::CUniquePointer m_impl; friend class CComboboxBuilder; friend class CComboboxClickable; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Element.hpp000066400000000000000000000103401513450241200254500ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include "../types/PointerShape.hpp" #include "../palette/Color.hpp" #include "../core/Input.hpp" #include "../core/CoreMacros.hpp" namespace Hyprtoolkit { struct SElementInternalData; using colorFn = std::function; class IElement { public: enum ePositionMode : uint8_t { HT_POSITION_ABSOLUTE = 0, HT_POSITION_AUTO, }; enum ePositionFlag : uint8_t { HT_POSITION_FLAG_HCENTER = (1 << 0), HT_POSITION_FLAG_VCENTER = (1 << 1), HT_POSITION_FLAG_CENTER = HT_POSITION_FLAG_HCENTER | HT_POSITION_FLAG_VCENTER, HT_POSITION_FLAG_LEFT = (1 << 2), HT_POSITION_FLAG_RIGHT = (1 << 3), HT_POSITION_FLAG_TOP = (1 << 4), HT_POSITION_FLAG_BOTTOM = (1 << 5), HT_POSITION_FLAG_ALL = 0xFF, }; virtual ~IElement(); virtual void paint() = 0; virtual Hyprutils::Math::Vector2D size() = 0; virtual Hyprutils::Math::Vector2D posFromParent(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); // TODO: move this to builders, this is clunky virtual void setPositionMode(ePositionMode mode); virtual void setPositionFlag(ePositionFlag flag, bool set); virtual void setAbsolutePosition(const Hyprutils::Math::Vector2D& offset); virtual void addChild(Hyprutils::Memory::CSharedPointer child); virtual void removeChild(Hyprutils::Memory::CSharedPointer child); virtual void clearChildren(); virtual void setMargin(float thick); virtual void setGrouped(bool grouped); // this will make this element get mouse input, then you can get events virtual void setReceivesMouse(bool x); virtual void setMouseEnter(std::function&& fn); virtual void setMouseLeave(std::function&& fn); virtual void setMouseMove(std::function&& fn); virtual void setMouseButton(std::function&& fn); virtual void setMouseAxis(std::function&& fn); virtual void setTooltip(std::string&&); virtual void setRepositioned(std::function&& fn); virtual void setGrow(bool grow); virtual void setGrow(bool growH, bool growV); // forces a reposition right now, useful for pre-calculating expected sizes virtual void forceReposition(); HT_HIDDEN : /* Sizes for auto positioning in layouts */ virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual bool acceptsKeyboardInput(); virtual ePointerShape pointerShape(); virtual std::function pointerShapeFn(); virtual bool alwaysGetMouseInput(); virtual void imCommitNewText(const std::string& text); virtual void imApplyText(); virtual void recheckColor(); virtual bool positioningDependsOnChild(); virtual Hyprutils::Math::CBox opaqueBox(); // Hyprutils::Memory::CUniquePointer impl; protected: IElement(); }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Image.hpp000066400000000000000000000056211513450241200251070ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include "../types/ImageTypes.hpp" #include #include namespace Hyprtoolkit { class IRendererTexture; struct SImageImpl; struct SImageData; class CImageElement; class ISystemIconDescription; class CImageBuilder { public: ~CImageBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer path(std::string&&); Hyprutils::Memory::CSharedPointer icon(const Hyprutils::Memory::CSharedPointer&); Hyprutils::Memory::CSharedPointer data(std::vector&& data); Hyprutils::Memory::CSharedPointer a(float); Hyprutils::Memory::CSharedPointer fitMode(eImageFitMode); Hyprutils::Memory::CSharedPointer sync(bool); Hyprutils::Memory::CSharedPointer rounding(int); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CImageBuilder() = default; friend class CImageElement; }; class CImageElement : public IElement { public: virtual ~CImageElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: CImageElement(const SImageData& data); static Hyprutils::Memory::CSharedPointer create(const SImageData& data); void replaceData(const SImageData& data); // virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); void renderTex(); Hyprutils::Memory::CUniquePointer m_impl; friend class CImageBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Line.hpp000066400000000000000000000045761513450241200247640ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include "../palette/Color.hpp" #include namespace Hyprtoolkit { struct SLineImpl; struct SLineData; class CLineElement; class CLineBuilder { public: ~CLineBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer color(colorFn&&); Hyprutils::Memory::CSharedPointer thick(int); Hyprutils::Memory::CSharedPointer points(std::vector&&); // [0, 0] - [1, 1] Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CLineBuilder() = default; friend class CLineElement; }; class CLineElement : public IElement { public: virtual ~CLineElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: static Hyprutils::Memory::CSharedPointer create(const SLineData& data); CLineElement(const SLineData& data); void replaceData(const SLineData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); virtual void recheckColor(); Hyprutils::Memory::CUniquePointer m_impl; friend class CLineBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Null.hpp000066400000000000000000000041051513450241200247730ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include "../palette/Color.hpp" #include namespace Hyprtoolkit { struct SNullData; struct SNullImpl; class CNullElement; class CNullBuilder { public: ~CNullBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CNullBuilder() = default; friend class CNullElement; }; class CNullElement : public IElement { public: virtual ~CNullElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: CNullElement(const SNullData& data); static Hyprutils::Memory::CSharedPointer create(const SNullData& data); void replaceData(const SNullData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CNullBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Rectangle.hpp000066400000000000000000000052001513450241200257620ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include "../palette/Color.hpp" #include namespace Hyprtoolkit { struct SRectangleImpl; struct SRectangleData; class CRectangleElement; class CRectangleBuilder { public: ~CRectangleBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer color(colorFn&&); Hyprutils::Memory::CSharedPointer borderColor(colorFn&&); Hyprutils::Memory::CSharedPointer rounding(int); Hyprutils::Memory::CSharedPointer borderThickness(int); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CRectangleBuilder() = default; friend class CRectangleElement; }; class CRectangleElement : public IElement { public: virtual ~CRectangleElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: static Hyprutils::Memory::CSharedPointer create(const SRectangleData& data); CRectangleElement(const SRectangleData& data); void replaceData(const SRectangleData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); virtual void recheckColor(); virtual Hyprutils::Math::CBox opaqueBox(); Hyprutils::Memory::CUniquePointer m_impl; friend class CRectangleBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/RowLayout.hpp000066400000000000000000000047751513450241200260430ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" namespace Hyprtoolkit { struct SRowLayoutData; struct SRowLayoutImpl; class CRowLayoutElement; class CRowLayoutBuilder { public: ~CRowLayoutBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer gap(size_t gap); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CRowLayoutBuilder() = default; friend class CRowLayoutElement; }; class CRowLayoutElement : public IElement { public: virtual ~CRowLayoutElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); private: CRowLayoutElement(const SRowLayoutData& data); static Hyprutils::Memory::CSharedPointer create(const SRowLayoutData& data); void replaceData(const SRowLayoutData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); Hyprutils::Math::Vector2D childSize(Hyprutils::Memory::CSharedPointer child); Hyprutils::Memory::CUniquePointer m_impl; friend class CCheckboxElement; friend class CSpinboxElement; friend class CSpinboxSpinner; friend class CRowLayoutBuilder; friend class CSliderSlider; friend class CSliderElement; friend class CComboboxElement; friend class CComboboxClickable; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/ScrollArea.hpp000066400000000000000000000054051513450241200261140ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" namespace Hyprtoolkit { struct SScrollAreaData; struct SScrollAreaImpl; class CScrollAreaElement; class CScrollAreaBuilder { public: ~CScrollAreaBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer scrollX(bool); Hyprutils::Memory::CSharedPointer scrollY(bool); Hyprutils::Memory::CSharedPointer blockUserScroll(bool); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CScrollAreaBuilder() = default; friend class CScrollAreaElement; }; class CScrollAreaElement : public IElement { public: virtual ~CScrollAreaElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); Hyprutils::Math::Vector2D getCurrentScroll(); void setScroll(const Hyprutils::Math::Vector2D&); private: CScrollAreaElement(const SScrollAreaData& data); static Hyprutils::Memory::CSharedPointer create(const SScrollAreaData& data); void replaceData(const SScrollAreaData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual bool alwaysGetMouseInput(); virtual ePointerShape pointerShape(); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CScrollAreaBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Slider.hpp000066400000000000000000000056061513450241200253120ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include #include #include namespace Hyprtoolkit { struct SSliderImpl; struct SSliderData; class CSliderElement; class CSliderBuilder { public: ~CSliderBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer onChanged(std::function, float)>&&); Hyprutils::Memory::CSharedPointer min(float); Hyprutils::Memory::CSharedPointer max(float); Hyprutils::Memory::CSharedPointer val(float); Hyprutils::Memory::CSharedPointer snapInt(bool); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CSliderBuilder() = default; friend class CSliderElement; }; class CSliderElement : public IElement { public: virtual ~CSliderElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); bool sliding(); private: CSliderElement(const SSliderData& data); static Hyprutils::Memory::CSharedPointer create(const SSliderData& data); void replaceData(const SSliderData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CSliderBuilder; friend class CSliderSlider; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Spinbox.hpp000066400000000000000000000062141513450241200255060ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include #include #include #include namespace Hyprtoolkit { struct SSpinboxImpl; struct SSpinboxData; class CSpinboxElement; class CSpinboxBuilder { public: ~CSpinboxBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer label(std::string&&); Hyprutils::Memory::CSharedPointer items(std::vector&&); Hyprutils::Memory::CSharedPointer currentItem(size_t); Hyprutils::Memory::CSharedPointer onChanged(std::function, size_t)>&&); Hyprutils::Memory::CSharedPointer fill(bool); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CSpinboxBuilder() = default; friend class CSpinboxElement; }; class CSpinboxElement : public IElement { public: virtual ~CSpinboxElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); size_t current(); void setCurrent(size_t current); private: CSpinboxElement(const SSpinboxData& data); static Hyprutils::Memory::CSharedPointer create(const SSpinboxData& data); void replaceData(const SSpinboxData& data); void init(); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CSpinboxSpinner; friend class CSpinboxBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Text.hpp000066400000000000000000000067321513450241200250150ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include "../palette/Color.hpp" #include "../types/FontTypes.hpp" #include #include #include namespace Hyprtoolkit { class IRendererTexture; struct STextImpl; struct STextData; class CTextElement; class CTextBuilder { public: ~CTextBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer color(colorFn&&); Hyprutils::Memory::CSharedPointer a(float); Hyprutils::Memory::CSharedPointer fontSize(CFontSize&&); Hyprutils::Memory::CSharedPointer align(eFontAlignment); Hyprutils::Memory::CSharedPointer text(std::string&&); Hyprutils::Memory::CSharedPointer fontFamily(std::string&&); Hyprutils::Memory::CSharedPointer clampSize(Hyprutils::Math::Vector2D&&); Hyprutils::Memory::CSharedPointer callback(std::function&&); Hyprutils::Memory::CSharedPointer noEllipsize(bool); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer async(bool x); Hyprutils::Memory::CSharedPointer interactable(bool x); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CTextBuilder() = default; friend class CTextElement; }; class CTextElement : public IElement { public: virtual ~CTextElement(); Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); HT_HIDDEN : CTextElement(const STextData& data); static Hyprutils::Memory::CSharedPointer create(const STextData& data); void replaceData(const STextData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool positioningDependsOnChild(); virtual void recheckColor(); virtual bool acceptsMouseInput(); virtual std::function pointerShapeFn(); Hyprutils::Memory::CUniquePointer m_impl; friend class CButtonElement; friend class CTextBuilder; friend struct STextboxImpl; friend class CTextboxElement; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/element/Textbox.hpp000066400000000000000000000065451513450241200255300ustar00rootroot00000000000000#pragma once #include "Element.hpp" #include "../types/SizeType.hpp" #include #include namespace Hyprtoolkit { struct STextboxImpl; struct STextboxData; class CTextboxElement; class CTextboxBuilder { public: ~CTextboxBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer placeholder(std::string&&); Hyprutils::Memory::CSharedPointer defaultText(std::string&&); Hyprutils::Memory::CSharedPointer onTextEdited(std::function, const std::string&)>&&); Hyprutils::Memory::CSharedPointer multiline(bool); Hyprutils::Memory::CSharedPointer password(bool); Hyprutils::Memory::CSharedPointer size(CDynamicSize&&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; Hyprutils::Memory::CWeakPointer m_element; CTextboxBuilder() = default; friend class CTextboxElement; }; class CTextboxElement : public IElement { public: virtual ~CTextboxElement() = default; Hyprutils::Memory::CSharedPointer rebuild(); virtual Hyprutils::Math::Vector2D size(); void focus(bool focus = true); std::string_view currentText(); size_t cursorPos() const; std::tuple selection() const; private: static Hyprutils::Memory::CSharedPointer create(const STextboxData& data); CTextboxElement(const STextboxData& data); void replaceData(const STextboxData& data); void init(); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool acceptsKeyboardInput(); virtual void imCommitNewText(const std::string&); virtual void imApplyText(); virtual bool positioningDependsOnChild(); Hyprutils::Memory::CUniquePointer m_impl; friend class CTextboxBuilder; }; }; hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/palette/000077500000000000000000000000001513450241200233555ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/palette/Color.hpp000066400000000000000000000023251513450241200251460ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit { class CHyprColor { public: CHyprColor(); CHyprColor(float r, float g, float b, float a = 1.F); CHyprColor(const Hyprgraphics::CColor& col, float a); CHyprColor(uint64_t); // AR32 uint32_t getAsHex() const; Hyprgraphics::CColor::SSRGB asRGB() const; Hyprgraphics::CColor::SOkLab asOkLab() const; Hyprgraphics::CColor::SHSL asHSL() const; CHyprColor stripA() const; CHyprColor brighten(float coeff) const; CHyprColor darken(float coeff) const; CHyprColor mix(const CHyprColor& with, float coeff) const; // bool operator==(const CHyprColor& c2) const; // stubs for the AnimationMgr CHyprColor operator-(const CHyprColor& c2) const; CHyprColor operator+(const CHyprColor& c2) const; CHyprColor operator*(const float& c2) const; double r = 0, g = 0, b = 0, a = 0; private: Hyprgraphics::CColor::SOkLab okLab; // cache for the OkLab representation }; }hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/palette/Palette.hpp000066400000000000000000000027431513450241200254720ustar00rootroot00000000000000#pragma once #include #include "Color.hpp" #include namespace Hyprtoolkit { class CPalette { public: ~CPalette() = default; /* Get the best palette possible. Retrieves configured system palette if available, default otherwise */ static Hyprutils::Memory::CSharedPointer palette(); /* Empty palette with just black */ static Hyprutils::Memory::CSharedPointer emptyPalette(); struct { CHyprColor background; CHyprColor text; CHyprColor base; CHyprColor alternateBase; CHyprColor brightText; CHyprColor linkText; CHyprColor accent; CHyprColor accentSecondary; } m_colors; struct { int h1Size = 19; int h2Size = 15; int h3Size = 13; int fontSize = 11; int smallFontSize = 10; std::string iconTheme = ""; // first one found int bigRounding = 10; int smallRounding = 5; std::string fontFamily = "Sans Serif"; std::string fontFamilyMonospace = "monospace"; } m_vars; private: CPalette() = default; bool m_isConfig = false; friend class CBackend; }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/system/000077500000000000000000000000001513450241200232435ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/system/Icons.hpp000066400000000000000000000014051513450241200250270ustar00rootroot00000000000000#pragma once #include namespace Hyprtoolkit { class ISystemIconDescription { public: virtual ~ISystemIconDescription() = default; virtual bool exists() = 0; virtual bool scalable() = 0; protected: ISystemIconDescription() = default; }; class ISystemIconFactory { public: virtual ~ISystemIconFactory() = default; /* Lookup an icon. If the icon is found, will return associated data. This object can be used to create an ImageElement */ virtual Hyprutils::Memory::CSharedPointer lookupIcon(const std::string& iconName) = 0; protected: ISystemIconFactory() = default; }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/types/000077500000000000000000000000001513450241200230635ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/types/FontTypes.hpp000066400000000000000000000013221513450241200255250ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit { class CFontSize { public: enum eSizingBase : uint8_t { HT_FONT_H1, HT_FONT_H2, HT_FONT_H3, HT_FONT_TEXT, HT_FONT_SMALL, HT_FONT_ABSOLUTE, }; // in the case of ABSOLUTE, multiplier is the raw value. CFontSize(eSizingBase base, float multiplier = 1.F); float ptSize(); private: eSizingBase m_base = HT_FONT_TEXT; float m_value = 1.F; }; enum eFontAlignment : uint8_t { HT_FONT_ALIGN_LEFT, HT_FONT_ALIGN_CENTER, HT_FONT_ALIGN_RIGHT, }; }hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/types/ImageTypes.hpp000066400000000000000000000003461513450241200256460ustar00rootroot00000000000000 #pragma once #include namespace Hyprtoolkit { enum eImageFitMode : uint8_t { IMAGE_FIT_MODE_STRETCH = 0, IMAGE_FIT_MODE_COVER, IMAGE_FIT_MODE_CONTAIN, IMAGE_FIT_MODE_TILE, }; } hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/types/PointerShape.hpp000066400000000000000000000002701513450241200261740ustar00rootroot00000000000000#pragma once #include namespace Hyprtoolkit { enum ePointerShape : uint8_t { HT_POINTER_ARROW = 0, HT_POINTER_POINTER, HT_POINTER_TEXT, }; }hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/types/SizeType.hpp000066400000000000000000000013331513450241200253500ustar00rootroot00000000000000#pragma once #include #include #include "../core/CoreMacros.hpp" namespace Hyprtoolkit { class CDynamicSize { public: enum eSizingType : uint8_t { HT_SIZE_ABSOLUTE, HT_SIZE_PERCENT, HT_SIZE_AUTO, // contain child(ren) }; CDynamicSize(eSizingType typeX, eSizingType typeY, const Hyprutils::Math::Vector2D& size); Hyprutils::Math::Vector2D calculate(Hyprutils::Math::Vector2D elSize) const; HT_HIDDEN : bool hasAuto(); private: eSizingType m_typeX = HT_SIZE_ABSOLUTE, m_typeY = HT_SIZE_ABSOLUTE; Hyprutils::Math::Vector2D m_value; }; }hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/window/000077500000000000000000000000001513450241200232265ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/include/hyprtoolkit/window/Window.hpp000066400000000000000000000070661513450241200252170ustar00rootroot00000000000000#pragma once #include #include #include #include "../core/Input.hpp" namespace Hyprtoolkit { class IElement; class IWindow; class IOutput; struct SWindowCreationData; enum eWindowType : uint8_t { HT_WINDOW_TOPLEVEL = 0, HT_WINDOW_POPUP = 1, HT_WINDOW_LAYER = 2, HT_WINDOW_LOCK_SURFACE = 3, }; class CWindowBuilder { public: ~CWindowBuilder() = default; static Hyprutils::Memory::CSharedPointer begin(); Hyprutils::Memory::CSharedPointer type(eWindowType); Hyprutils::Memory::CSharedPointer appTitle(std::string&&); Hyprutils::Memory::CSharedPointer appClass(std::string&&); Hyprutils::Memory::CSharedPointer preferredSize(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer minSize(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer maxSize(const Hyprutils::Math::Vector2D&); // only for LAYER and LOCK_SURFACE Hyprutils::Memory::CSharedPointer prefferedOutput(const Hyprutils::Memory::CSharedPointer& output); // only for HT_WINDOW_LAYER Hyprutils::Memory::CSharedPointer marginTopLeft(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer marginBottomRight(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer layer(uint32_t); Hyprutils::Memory::CSharedPointer anchor(uint32_t); Hyprutils::Memory::CSharedPointer exclusiveEdge(uint32_t); Hyprutils::Memory::CSharedPointer exclusiveZone(int32_t); Hyprutils::Memory::CSharedPointer kbInteractive(uint32_t); // only for HT_WINDOW_POPUP Hyprutils::Memory::CSharedPointer parent(const Hyprutils::Memory::CSharedPointer& parent); Hyprutils::Memory::CSharedPointer pos(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer commence(); private: Hyprutils::Memory::CWeakPointer m_self; Hyprutils::Memory::CUniquePointer m_data; CWindowBuilder() = default; }; class IWindow { public: virtual ~IWindow() = default; virtual Hyprutils::Math::Vector2D pixelSize() = 0; virtual float scale() = 0; virtual void close() = 0; virtual void open() = 0; virtual Hyprutils::Math::Vector2D cursorPos() = 0; struct { // coordinates here are logical, meaning pixel size is this * scale() Hyprutils::Signal::CSignalT resized; // user requested a close. Hyprutils::Signal::CSignalT<> closeRequest; // popup closed Hyprutils::Signal::CSignalT<> popupClosed; // layer closed Hyprutils::Signal::CSignalT<> layerClosed; // (global) key events Hyprutils::Signal::CSignalT keyboardKey; } m_events; Hyprutils::Memory::CSharedPointer m_rootElement; HT_HIDDEN : IWindow() = default; }; }; hyprwm-hyprtoolkit-71515e8/nix/000077500000000000000000000000001513450241200165025ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/nix/default.nix000066400000000000000000000023411513450241200206460ustar00rootroot00000000000000{ lib, stdenv, cmake, pkg-config, aquamarine, cairo, epoll-shim, gtest, hyprgraphics, hyprlang, hyprutils, hyprwayland-scanner, iniparser, libGL, libdrm, libgbm, libxkbcommon, pango, pixman, wayland, wayland-protocols, wayland-scanner, version ? "git", doCheck ? false, }: let inherit (lib.lists) optional; inherit (lib.strings) optionalString; in stdenv.mkDerivation { pname = "hyprtoolkit" + optionalString doCheck "-with-tests"; inherit version doCheck; src = ../.; nativeBuildInputs = [ cmake pkg-config hyprwayland-scanner wayland-scanner ]; buildInputs = [ aquamarine cairo gtest hyprgraphics hyprlang hyprutils iniparser libGL libdrm libgbm libxkbcommon pango pixman wayland wayland-protocols ] ++ (optional stdenv.isBSD epoll-shim); env.XDG_RUNTIME_DIR = "/tmp/runtime"; cmakeBuildType = if doCheck then "Debug" else "RelWithDebInfo"; preCheck = '' mkdir -p /tmp/runtime ''; meta = { homepage = "https://github.com/hyprwm/hyprtoolkit"; description = "A modern C++ Wayland-native GUI toolkit"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; }; } hyprwm-hyprtoolkit-71515e8/nix/overlays.nix000066400000000000000000000017441513450241200210740ustar00rootroot00000000000000{ lib, inputs, self, }: let mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); in { default = inputs.self.overlays.hyprtoolkit; hyprtoolkit = lib.composeManyExtensions [ inputs.aquamarine.overlays.default inputs.hyprgraphics.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default (final: prev: { hyprtoolkit = prev.callPackage ./default.nix { stdenv = prev.gcc15Stdenv; version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); }; hyprtoolkit-with-tests = final.hyprtoolkit.override { doCheck = true; }; }) ]; } hyprwm-hyprtoolkit-71515e8/protocols/000077500000000000000000000000001513450241200177305ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/protocols/.gitkeep000066400000000000000000000000001513450241200213470ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/protocols/wlr-layer-shell-unstable-v1.xml000066400000000000000000000455761513450241200256550ustar00rootroot00000000000000 Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. Requests an edge for the exclusive zone to apply. The exclusive edge will be automatically deduced from anchor points when possible, but when the surface is anchored to a corner, it will be necessary to set it explicitly to disambiguate, as it is not possible to deduce which one of the two corner edges should be used. The edge must be one the surface is anchored to, otherwise the invalid_exclusive_edge protocol error will be raised. hyprwm-hyprtoolkit-71515e8/scripts/000077500000000000000000000000001513450241200173735ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/scripts/generateShaderIncludes.sh000077500000000000000000000014751513450241200243510ustar00rootroot00000000000000#!/bin/sh SHADERS_SRC="./src/renderer/gl/shaders/glsl" echo "-- Generating shader includes" if [ ! -d ./src/renderer/shaders ]; then mkdir ./src/renderer/shaders fi echo '#pragma once' > ./src/renderer/gl/shaders/Shaders.hpp echo '#include ' >> ./src/renderer/gl/shaders/Shaders.hpp echo 'static const std::map SHADERS = {' >> ./src/renderer/gl/shaders/Shaders.hpp for filename in `ls ${SHADERS_SRC}`; do echo "-- ${filename}" { echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/renderer/gl/shaders/${filename}.inc echo "{\"${filename}\"," >> ./src/renderer/gl/shaders/Shaders.hpp echo "#include \"./${filename}.inc\"" >> ./src/renderer/gl/shaders/Shaders.hpp echo "}," >> ./src/renderer/gl/shaders/Shaders.hpp done echo '};' >> ./src/renderer/gl/shaders/Shaders.hpp hyprwm-hyprtoolkit-71515e8/src/000077500000000000000000000000001513450241200164735ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/Macros.hpp000066400000000000000000000051511513450241200204320ustar00rootroot00000000000000#pragma once #include #include #include #include "./helpers/Env.hpp" #define TRACE(expr) \ { \ if (Hyprtoolkit::Env::isTrace()) { \ expr; \ } \ } #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ std::cout << std::format("\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ std::format(reason, ##__VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ std::cout << "[HT] Assertion failed!"; \ std::fflush(stdout); \ raise(SIGABRT); \ } #define ASSERT(expr) RASSERT(expr, "?") #ifndef HYPRTOOLKIT_DEBUG #define UNREACHABLE() std::unreachable(); #else #define UNREACHABLE() RASSERT(false, "Reached an unreachable block"); #endifhyprwm-hyprtoolkit-71515e8/src/core/000077500000000000000000000000001513450241200174235ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/core/AnimatedVariable.hpp000066400000000000000000000042251513450241200233270ustar00rootroot00000000000000#pragma once #include #include #include #include "../helpers/Memory.hpp" namespace Hyprtoolkit { enum eAnimatedVarType : int8_t { AVARTYPE_INVALID = -1, AVARTYPE_FLOAT, AVARTYPE_VECTOR, AVARTYPE_COLOR, AVARTYPE_GRADIENT }; // Utility to bind a type with its corresponding eAnimatedVarType template // NOLINTNEXTLINE(readability-identifier-naming) struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_INVALID; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_FLOAT; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_VECTOR; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_COLOR; }; // TODO: // template <> // struct STypeToAnimatedVarType_t { // static constexpr eAnimatedVarType value = AVARTYPE_GRADIENT; // }; template inline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t::value; // Utility to define a concept as a list of possible type template concept OneOf = (... or std::same_as); // Concept to describe which type can be placed into CAnimatedVariable // This is mainly to get better errors if we put a type that's not supported // Otherwise template errors are ugly template concept Animable = OneOf; struct SAnimationContext {}; template using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable; template using PHLANIMVAR = UP>; template using PHLANIMVARREF = WP>; } hyprwm-hyprtoolkit-71515e8/src/core/AnimationManager.cpp000066400000000000000000000115741513450241200233510ustar00rootroot00000000000000#include "AnimationManager.hpp" #include "../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CHTAnimationManager::CHTAnimationManager() { addBezierWithName("linear", {0, 0}, {1, 1}); addBezierWithName("easeOutQuint", {0.23F, 1.F}, {0.32F, 1.F}); m_animationTree.createNode("global"); m_animationTree.createNode("fast", "global"); m_animationTree.setConfigForNode("fast", 1, 3.3F, "easeOutQuint"); } template static void updateVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { if (warp || !av.enabled() || av.value() == av.goal()) { av.warp(true, false); return; } const auto DELTA = av.goal() - av.begun(); av.value() = av.begun() + DELTA * POINTY; } static void updateColorVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { if (warp || !av.enabled() || av.value() == av.goal()) { av.warp(true, false); return; } // convert both to OkLab, then lerp that, and convert back. // This is not as fast as just lerping rgb, but it's WAY more precise... // Use the CHyprColor cache for OkLab const auto& L1 = av.begun().asOkLab(); const auto& L2 = av.goal().asOkLab(); static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); }; const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{ .l = lerp(L1.l, L2.l, POINTY), .a = lerp(L1.a, L2.a, POINTY), .b = lerp(L1.b, L2.b, POINTY), }; av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)}; } // void updateGradientVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { // if (warp || av.value() == av.goal()) { // av.warp(true, false); // return; // } // av.value().m_vColors.resize(av.goal().m_vColors.size(), av.goal().m_vColors.back()); // for (size_t i = 0; i < av.value().m_vColors.size(); ++i) { // const CHyprColor& sourceCol = (i < av.begun().m_vColors.size()) ? av.begun().m_vColors[i] : av.begun().m_vColors.back(); // const CHyprColor& targetCol = (i < av.goal().m_vColors.size()) ? av.goal().m_vColors[i] : av.goal().m_vColors.back(); // const auto& L1 = sourceCol.asOkLab(); // const auto& L2 = targetCol.asOkLab(); // static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); }; // const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{ // .l = lerp(L1.l, L2.l, POINTY), // .a = lerp(L1.a, L2.a, POINTY), // .b = lerp(L1.b, L2.b, POINTY), // }; // av.value().m_vColors[i] = {lerped, lerp(sourceCol.a, targetCol.a, POINTY)}; // } // if (av.begun().m_fAngle != av.goal().m_fAngle) { // const float DELTA = av.goal().m_fAngle - av.begun().m_fAngle; // av.value().m_fAngle = av.begun().m_fAngle + DELTA * POINTY; // } // av.value().updateColorsOk(); // } void CHTAnimationManager::tick() { for (const auto& PAV : m_vActiveAnimatedVariables) { if (!PAV || !PAV->ok()) continue; const auto SPENT = PAV->getPercent(); const auto PBEZIER = getBezier(PAV->getBezierName()); const auto POINTY = PBEZIER->getYForPoint(SPENT); const bool WARP = SPENT >= 1.f; switch (PAV->m_Type) { case AVARTYPE_FLOAT: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated float"); updateVariable(*pTypedAV, POINTY, WARP); } break; case AVARTYPE_VECTOR: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); updateVariable(*pTypedAV, POINTY, WARP); } break; case AVARTYPE_COLOR: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); updateColorVariable(*pTypedAV, POINTY, WARP); } break; // case AVARTYPE_GRADIENT: { // auto pTypedAV = dynamic_cast*>(PAV.get()); // RASSERT(pTypedAV, "Failed to upcast animated CGradientValueData"); // updateGradientVariable(*pTypedAV, POINTY, WARP); // } break; default: continue; } PAV->onUpdate(); } tickDone(); } void CHTAnimationManager::scheduleTick() { ; } void CHTAnimationManager::onTicked() { ; } hyprwm-hyprtoolkit-71515e8/src/core/AnimationManager.hpp000066400000000000000000000021311513450241200233430ustar00rootroot00000000000000#pragma once #include #include #include "AnimatedVariable.hpp" namespace Hyprtoolkit { class CHTAnimationManager : public Hyprutils::Animation::CAnimationManager { public: CHTAnimationManager(); void tick(); virtual void scheduleTick(); virtual void onTicked(); using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig; template void createAnimation(const VarType& v, PHLANIMVAR& pav, SP pConfig) { constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType; pav = makeUnique>(); pav->create2(EAVTYPE, static_cast(this), pav, v); pav->setConfig(pConfig); } Hyprutils::Animation::CAnimationConfigTree m_animationTree; }; inline SP g_animationManager; } hyprwm-hyprtoolkit-71515e8/src/core/Backend.cpp000066400000000000000000000402231513450241200214570ustar00rootroot00000000000000#include #include #include #include #include "InternalBackend.hpp" #include "AnimationManager.hpp" #include "./platforms/WaylandPlatform.hpp" #include "../renderer/gl/OpenGL.hpp" #include "../output/WaylandOutput.hpp" #include "../window/WaylandLayer.hpp" #include "../window/WaylandLockSurface.hpp" #include "../window/WaylandWindow.hpp" #include "../Macros.hpp" #include "../element/Element.hpp" #include "../palette/ConfigManager.hpp" #include "../system/Icons.hpp" #include "../sessionLock/WaylandSessionLock.hpp" #include #include #include #include #if defined(__FreeBSD__) #include #elif defined(__NetBSD__) #include #elif defined(__DragonFly__) #include #endif using namespace Hyprtoolkit; using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer CBackend::CBackend() { pipe(m_sLoopState.exitfd); pipe(m_sLoopState.wakeupfd); Aquamarine::SBackendOptions options{}; g_logger->m_aqLoggerConnection = makeShared(g_logger->m_logger); g_logger->m_aqLoggerConnection->setLogLevel(Hyprutils::CLI::LOG_WARN); // don't print debug logs, unless AQ_TRACE is set, then aq will set it g_logger->m_aqLoggerConnection->setName("aquamarine"); options.logConnection = g_logger->m_aqLoggerConnection; std::vector implementations; Aquamarine::SBackendImplementationOptions option; option.backendType = Aquamarine::eBackendType::AQ_BACKEND_NULL; option.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_MANDATORY; implementations.emplace_back(option); m_aqBackend = Aquamarine::CBackend::create(implementations, options); g_asyncResourceGatherer = makeShared(); g_animationManager = makeShared(); } CBackend::~CBackend() { destroy(); g_openGL.reset(); g_renderer.reset(); g_config.reset(); g_palette.reset(); g_iconFactory.reset(); g_waylandPlatform.reset(); g_logger.reset(); close(m_sLoopState.exitfd[0]); close(m_sLoopState.exitfd[1]); close(m_sLoopState.wakeupfd[0]); close(m_sLoopState.wakeupfd[1]); } IBackend::SBackendCreationData::SBackendCreationData() = default; SP IBackend::createWithData(const IBackend::SBackendCreationData& data) { g_logger = makeShared(); g_logger->m_loggerConnection = data.pLogConnection; g_logger->updateLogLevel(); return IBackend::create(); } SP IBackend::create() { if (g_backend) return nullptr; if (!g_logger) g_logger = makeShared(); g_backend = SP(new CBackend()); g_config = makeShared(); g_config->parse(); g_palette = CPalette::palette(); g_iconFactory = SP(new CSystemIconFactory()); if (!g_backend->m_aqBackend || !g_backend->m_aqBackend->start()) { g_logger->log(HT_LOG_ERROR, "couldn't start aq backend"); g_backend.reset(); return nullptr; } g_waylandPlatform = makeUnique(); if (!g_waylandPlatform->attempt()) { g_waylandPlatform = nullptr; g_backend.reset(); return nullptr; } g_openGL = makeShared(g_waylandPlatform->m_drmState.fd); g_renderer = g_openGL; return g_backend; }; void CBackend::destroy() { terminate(); } void CBackend::setLogFn(LogFn&& fn) { g_logger->m_logFn = std::move(fn); } SP CBackend::getPalette() { return g_palette; } std::vector> CBackend::getOutputs() { if (!g_waylandPlatform) return {}; return std::vector>(g_waylandPlatform->m_outputs.begin(), g_waylandPlatform->m_outputs.end()); } std::expected, eSessionLockError> CBackend::aquireSessionLock() { if (!g_waylandPlatform) return std::unexpected(LOCK_ERROR_PLATFORM_UNINITIALIZED); auto lockState = g_waylandPlatform->aquireSessionLock(); if (!lockState || lockState->m_denied) return std::unexpected(LOCK_ERROR_DENIED); return lockState; } SP CBackend::openWindow(const SWindowCreationData& data) { if (!g_waylandPlatform) return nullptr; if (data.type == HT_WINDOW_LAYER) { if (!g_waylandPlatform->m_waylandState.layerShell) return nullptr; auto w = makeShared(data); w->m_self = w; w->m_rootElement->impl->window = w; g_waylandPlatform->m_layers.emplace_back(w); return w; } else if (data.type == HT_WINDOW_LOCK_SURFACE) { if (!g_waylandPlatform->m_waylandState.sessionLock) { g_logger->log(HT_LOG_ERROR, "No session lock manager. Does your compositor support it?"); return nullptr; } if (!g_waylandPlatform->m_sessionLockState || g_waylandPlatform->m_sessionLockState->m_denied || g_waylandPlatform->m_sessionLockState->m_sessionUnlocked) return nullptr; auto w = makeShared(data); w->m_self = w; w->m_rootElement->impl->window = w; g_waylandPlatform->m_sessionLockState->m_lockSurfaces.emplace_back(w); return w; } auto w = makeShared(data); w->m_self = w; w->m_rootElement->impl->window = w; g_waylandPlatform->m_windows.emplace_back(w); return w; } ASP CBackend::addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force) { std::lock_guard lg(m_sLoopState.timersMutex); const auto T = m_timers.emplace_back(makeAtomicShared(timeout, cb_, data, force)); m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); return T; } void CBackend::addIdle(const std::function& fn) { std::lock_guard lg(m_sLoopState.idlesMutex); m_idles.emplace_back(makeAtomicShared>(fn)); m_sLoopState.idleEvent = true; m_sLoopState.idleCV.notify_all(); } void CBackend::terminate() { if (m_terminate) return; m_terminate = true; if (m_sLoopState.eventLoopMutex.try_lock()) { m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); m_sLoopState.eventLoopMutex.unlock(); } if (m_sLoopState.eventLoopThreadID == -1) { // we are not in a thread loop at all, so we g_renderer.reset(); g_openGL.reset(); g_waylandPlatform.reset(); g_asyncResourceGatherer.reset(); g_animationManager.reset(); g_palette.reset(); g_backend.reset(); g_logger.reset(); } } SP CBackend::systemIcons() { return g_iconFactory; } static void reloadRecurse(SP el) { for (const auto& e : el->impl->children) { if (!e) continue; e->recheckColor(); reloadRecurse(e); } } void CBackend::reloadTheme() { if (g_palette->m_isConfig) g_palette = CPalette::palette(); for (const auto& w : g_waylandPlatform->m_windows) { if (!w) continue; reloadRecurse(w->m_rootElement); for (const auto& p : w->m_popups) { if (!p) continue; reloadRecurse(p->m_rootElement); } } for (const auto& w : g_waylandPlatform->m_layers) { if (!w) continue; reloadRecurse(w->m_rootElement); for (const auto& p : w->m_popups) { if (!p) continue; reloadRecurse(p->m_rootElement); } } } void CBackend::addFd(int fd, std::function&& callback) { m_sLoopState.userFds.emplace_back(SFDListener{ .fd = fd, .callback = std::move(callback), }); rebuildPollfds(); } void CBackend::removeFd(int fd) { std::erase_if(m_sLoopState.userFds, [fd](const auto& e) { return e.fd == fd; }); rebuildPollfds(); } void CBackend::doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function&& fn) { int fdInt = fd.get(); m_sLoopState.userFds.emplace_back(SFDListener{ .fdOwned = std::move(fd), .fd = fdInt, .callback = std::move(fn), .removeOnFire = true, }); rebuildPollfds(); } constexpr size_t INTERNAL_FDS = 4; void CBackend::rebuildPollfds(bool wakeup) { m_pollfds.resize(INTERNAL_FDS + m_sLoopState.userFds.size()); m_pollfds[0] = { .fd = wl_display_get_fd(g_waylandPlatform->m_waylandState.display), .events = POLLIN, }; m_pollfds[1] = { .fd = m_sLoopState.exitfd[0], .events = POLLIN, }; m_pollfds[2] = { .fd = g_config->m_inotifyFd.get(), .events = POLLIN, }; m_pollfds[3] = { .fd = m_sLoopState.wakeupfd[0], .events = POLLIN, }; int i = INTERNAL_FDS; for (const auto& uf : m_sLoopState.userFds) { m_pollfds[i++] = { .fd = uf.fd, .events = POLLIN, }; } if (wakeup) write(m_sLoopState.wakeupfd[1], "hello", 5); } void CBackend::enterLoop() { rebuildPollfds(); std::thread pollThr([this]() { while (!m_terminate) { bool preparedToRead = wl_display_prepare_read(g_waylandPlatform->m_waylandState.display) == 0; int events = 0; if (preparedToRead) { events = poll(m_pollfds.data(), m_pollfds.size(), 5000); if (m_terminate) return; if (events < 0) { RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno); wl_display_cancel_read(g_waylandPlatform->m_waylandState.display); continue; } for (size_t i = 0; i < 1; ++i) { RASSERT(!(m_pollfds[i].revents & POLLHUP), "[core] Disconnected from pollfd id {}", i); } wl_display_read_events(g_waylandPlatform->m_waylandState.display); m_sLoopState.wlDispatched = false; } m_needsConfigReload = m_pollfds[2].revents & POLLIN; if (m_pollfds[3].revents & POLLIN) { // clear the wakeup fd static std::array buf; read(m_pollfds[3].fd, buf.data(), 1023); m_pollfds[3].revents &= ~POLLIN; } for (size_t i = INTERNAL_FDS; i < m_pollfds.size(); ++i) { if (m_pollfds[i].revents & POLLIN) m_sLoopState.userFds[i - INTERNAL_FDS].needsDispatch = true; } if (events > 0 || !preparedToRead || m_needsConfigReload || (m_pollfds[3].revents & POLLIN) /* wakeup fd */) { std::unique_lock lk(m_sLoopState.eventLoopMutex); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); m_sLoopState.wlDispatchCV.wait_for(lk, std::chrono::milliseconds(100), [this] { return m_sLoopState.wlDispatched; }); } } }); std::thread timersThr([this]() { while (!m_terminate) { // calc nearest thing m_sLoopState.timersMutex.lock(); float least = 10000; for (auto& t : m_timers) { const auto TIME = std::clamp(t->leftMs(), 1.f, INFINITY); least = std::min(TIME, least); } m_sLoopState.timersMutex.unlock(); std::unique_lock lk(m_sLoopState.timerRequestMutex); m_sLoopState.timerCV.wait_for(lk, std::chrono::milliseconds((int)least + 1), [this] { return m_sLoopState.timerEvent; }); m_sLoopState.timerEvent = false; // notify main std::lock_guard lg2(m_sLoopState.eventLoopMutex); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); } }); std::thread idleThr([this]() { while (!m_terminate) { std::unique_lock lk(m_sLoopState.timerRequestMutex); m_sLoopState.idleCV.wait(lk, [this] { return m_sLoopState.idleEvent; }); m_sLoopState.idleEvent = false; // notify main std::lock_guard lg2(m_sLoopState.eventLoopMutex); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); } }); m_sLoopState.event = true; // let it process once m_sLoopState.eventLoopThreadID = #if defined(__linux__) gettid(); #elif defined(__FreeBSD__) pthread_getthreadid_np(); #elif defined(__OpenBSD__) getthrid(); #elif defined(__NetBSD__) _lwp_self(); #elif defined(__DragonFly__) lwp_gettid(); #endif while (!m_terminate) { std::unique_lock lk(m_sLoopState.eventRequestMutex); if (!m_sLoopState.event) m_sLoopState.loopCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sLoopState.event; }); if (m_terminate) break; std::lock_guard lg(m_sLoopState.eventLoopMutex); m_sLoopState.event = false; wl_display_dispatch_pending(g_waylandPlatform->m_waylandState.display); wl_display_flush(g_waylandPlatform->m_waylandState.display); m_sLoopState.wlDispatched = true; m_sLoopState.wlDispatchCV.notify_all(); // do timers m_sLoopState.timersMutex.lock(); auto timerscpy = m_timers; m_sLoopState.timersMutex.unlock(); std::vector> passed; for (auto& t : timerscpy) { if (t->passed() && !t->cancelled()) { t->call(t); passed.push_back(t); } if (t->cancelled()) passed.push_back(t); } m_sLoopState.timersMutex.lock(); std::erase_if(m_timers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); }); m_sLoopState.timersMutex.unlock(); passed.clear(); // do idles m_sLoopState.idlesMutex.lock(); auto idlesCpy = m_idles; m_idles.clear(); m_sLoopState.idlesMutex.unlock(); while (!idlesCpy.empty()) { for (const auto& i : idlesCpy) { (*i)(); } m_sLoopState.idlesMutex.lock(); idlesCpy = m_idles; m_idles.clear(); m_sLoopState.idlesMutex.unlock(); } if (m_needsConfigReload) { m_needsConfigReload = false; g_config->onInotifyEvent(); reloadTheme(); } // do user fds std::vector expiredFds; for (auto& uf : m_sLoopState.userFds) { if (!uf.needsDispatch) continue; uf.needsDispatch = false; uf.callback(); if (uf.removeOnFire) expiredFds.emplace_back(uf.fd); } if (!expiredFds.empty()) { std::erase_if(m_sLoopState.userFds, [&expiredFds](const auto& e) { return std::ranges::contains(expiredFds, e.fd); }); rebuildPollfds(false); } } m_sLoopState.eventLoopThreadID = -1; g_renderer.reset(); g_openGL.reset(); g_waylandPlatform.reset(); g_asyncResourceGatherer.reset(); g_animationManager.reset(); g_palette.reset(); g_backend.reset(); g_logger.reset(); m_sLoopState.idleEvent = true; m_sLoopState.idleCV.notify_all(); m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); write(m_sLoopState.exitfd[1], "hello", 5); if (timersThr.joinable()) timersThr.join(); if (idleThr.joinable()) idleThr.join(); if (pollThr.joinable()) pollThr.join(); } hyprwm-hyprtoolkit-71515e8/src/core/Backend.hpp000066400000000000000000000070761513450241200214750ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../helpers/Env.hpp" #include "../helpers/Memory.hpp" namespace Hyprtoolkit { class CPalette; class CConfigManager; class CSystemIconFactory; class CBackend : public IBackend { public: CBackend(); virtual ~CBackend(); virtual void destroy(); virtual void setLogFn(LogFn&& fn); virtual void addFd(int fd, std::function&& callback); virtual void removeFd(int fd); virtual SP systemIcons(); virtual ASP addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false); virtual void addIdle(const std::function& fn); virtual void enterLoop(); virtual std::vector> getOutputs(); virtual SP getPalette(); virtual std::expected, eSessionLockError> aquireSessionLock(); // ======================= Internal fns ======================= // void terminate(); void reloadTheme(); void rebuildPollfds(bool wakeup = true); // schedule function to when fd is readable (WL_EVENT_READABLE / POLLIN), // takes ownership of fd void doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function&& fn); SP openWindow(const SWindowCreationData& data); // std::vector m_pollfds; Hyprutils::Memory::CSharedPointer m_aqBackend; bool m_terminate = false; bool m_needsConfigReload = false; struct SFDListener { Hyprutils::OS::CFileDescriptor fdOwned; int fd = 0; std::function callback; bool needsDispatch = false; bool removeOnFire = false; }; struct { std::mutex timersMutex; std::mutex idlesMutex; std::mutex eventRequestMutex; std::mutex eventLoopMutex; std::condition_variable loopCV; bool event = false; std::condition_variable wlDispatchCV; bool wlDispatched = false; std::condition_variable timerCV; std::mutex timerRequestMutex; bool timerEvent = false; std::condition_variable idleCV; std::mutex idleRequestMutex; bool idleEvent = false; int exitfd[2]; int wakeupfd[2]; std::vector userFds; int64_t eventLoopThreadID = -1; } m_sLoopState; std::vector> m_timers; std::vector>> m_idles; }; } hyprwm-hyprtoolkit-71515e8/src/core/Input.cpp000066400000000000000000000005371513450241200212330ustar00rootroot00000000000000#include "Input.hpp" using namespace Hyprtoolkit; Input::eMouseButton Input::buttonFromWayland(uint32_t wl) { switch (wl) { case 272: return MOUSE_BUTTON_LEFT; case 273: return MOUSE_BUTTON_RIGHT; case 274: return MOUSE_BUTTON_MIDDLE; default: return MOUSE_BUTTON_UNKNOWN; } return MOUSE_BUTTON_UNKNOWN; }hyprwm-hyprtoolkit-71515e8/src/core/Input.hpp000066400000000000000000000002541513450241200212340ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprtoolkit::Input { eMouseButton buttonFromWayland(uint32_t wl); }hyprwm-hyprtoolkit-71515e8/src/core/InternalBackend.cpp000066400000000000000000000003031513450241200231470ustar00rootroot00000000000000#include "InternalBackend.hpp" #include "../helpers/Env.hpp" #include #include using namespace Hyprtoolkit; IBackend::~IBackend() = default; IBackend::IBackend() = default; hyprwm-hyprtoolkit-71515e8/src/core/InternalBackend.hpp000066400000000000000000000014701513450241200231620ustar00rootroot00000000000000#pragma once #include #include #include #include "Backend.hpp" #include "../helpers/Env.hpp" #include "Logger.hpp" namespace Hyprtoolkit { class CPalette; class CConfigManager; class CSystemIconFactory; inline Hyprutils::Memory::CSharedPointer g_backend; inline Hyprutils::Memory::CSharedPointer g_asyncResourceGatherer; inline Hyprutils::Memory::CSharedPointer g_palette; inline Hyprutils::Memory::CSharedPointer g_config; inline Hyprutils::Memory::CSharedPointer g_iconFactory; } hyprwm-hyprtoolkit-71515e8/src/core/Logger.cpp000066400000000000000000000023671513450241200213560ustar00rootroot00000000000000#include "Logger.hpp" using namespace Hyprtoolkit; static Hyprutils::CLI::eLogLevel levelToHU(eLogLevel l) { switch (l) { case Hyprtoolkit::HT_LOG_DEBUG: return Hyprutils::CLI::LOG_DEBUG; case Hyprtoolkit::HT_LOG_ERROR: return Hyprutils::CLI::LOG_ERR; case Hyprtoolkit::HT_LOG_WARNING: return Hyprutils::CLI::LOG_WARN; case Hyprtoolkit::HT_LOG_CRITICAL: return Hyprutils::CLI::LOG_CRIT; case Hyprtoolkit::HT_LOG_TRACE: return Hyprutils::CLI::LOG_TRACE; } return Hyprutils::CLI::LOG_DEBUG; } CLogger::CLogger() { const auto IS_TRACE = Env::isTrace(); m_logger.setLogLevel(IS_TRACE ? Hyprutils::CLI::LOG_TRACE : Hyprutils::CLI::LOG_DEBUG); } void CLogger::updateLogLevel() { const auto IS_TRACE = Env::isTrace(); if (m_loggerConnection && IS_TRACE) m_loggerConnection->setLogLevel(Hyprutils::CLI::LOG_TRACE); } void CLogger::log(eLogLevel level, const std::string& str) { static const bool IS_QUIET = Env::envEnabled("HT_QUIET"); if (IS_QUIET) return; if (m_logFn) { m_logFn(level, str); return; } if (m_loggerConnection) { m_loggerConnection->log(levelToHU(level), str); return; } m_logger.log(levelToHU(level), str); } hyprwm-hyprtoolkit-71515e8/src/core/Logger.hpp000066400000000000000000000033141513450241200213540ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../helpers/Env.hpp" namespace Hyprtoolkit { class CLogger { public: CLogger(); ~CLogger() = default; void log(eLogLevel level, const std::string& str); template //NOLINTNEXTLINE void log(eLogLevel level, std::format_string fmt, Args&&... args) { static bool LOG_DISABLED = Env::envEnabled("HT_NO_LOGS"); if (LOG_DISABLED) return; std::string logMsg = ""; // no need for try {} catch {} because std::format_string ensures that vformat never throw std::format_error // because // 1. any faulty format specifier that sucks will cause a compilation error. // 2. and `std::bad_alloc` is catastrophic, (Almost any operation in stdlib could throw this.) // 3. this is actually what std::format in stdlib does logMsg += std::vformat(fmt.get(), std::make_format_args(args...)); log(level, logMsg); } void updateLogLevel(); Hyprutils::CLI::CLogger m_logger; IBackend::LogFn m_logFn; Hyprutils::Memory::CSharedPointer m_loggerConnection; Hyprutils::Memory::CSharedPointer m_aqLoggerConnection; }; inline Hyprutils::Memory::CSharedPointer g_logger; };hyprwm-hyprtoolkit-71515e8/src/core/platforms/000077500000000000000000000000001513450241200214325ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/core/platforms/WaylandPlatform.cpp000066400000000000000000000705401513450241200252500ustar00rootroot00000000000000#include "WaylandPlatform.hpp" #include #include #include #include "../InternalBackend.hpp" #include "../Input.hpp" #include "../../element/Element.hpp" #include "../../window/WaylandWindow.hpp" #include "../../window/WaylandLayer.hpp" #include "../../window/WaylandLockSurface.hpp" #include "../../output/WaylandOutput.hpp" #include "../../sessionLock/WaylandSessionLock.hpp" #include "../../Macros.hpp" #include #include #include #include #include using namespace Hyprtoolkit; using namespace Hyprutils::Math; static std::string fourccToName(uint32_t drmFormat) { auto fmt = drmGetFormatName(drmFormat); std::string name = fmt ? fmt : "unknown"; free(fmt); return name; } bool CWaylandPlatform::attempt() { g_logger->log(HT_LOG_DEBUG, "Starting the Wayland platform"); m_waylandState.seatState.xkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); m_waylandState.display = wl_display_connect(nullptr); if (!m_waylandState.display) { g_logger->log(HT_LOG_ERROR, "Wayland platform cannot start: wl_display_connect failed (is a wayland compositor running?)"); return false; } auto XDGCURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP"); g_logger->log(HT_LOG_DEBUG, "Connected to a wayland compositor: {}", (XDGCURRENTDESKTOP ? XDGCURRENTDESKTOP : "unknown (XDG_CURRENT_DEKSTOP unset?)")); m_waylandState.registry = makeShared((wl_proxy*)wl_display_get_registry(m_waylandState.display)); g_logger->log(HT_LOG_DEBUG, "Got registry at 0x{:x}", (uintptr_t)m_waylandState.registry->resource()); m_waylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) { TRACE(g_logger->log(HT_LOG_TRACE, " | received global: {} (version {}) with id {}", name, version, id)); const std::string NAME = name; if (NAME == "wl_seat") { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 9, id)); m_waylandState.seat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wl_seat_interface, 9)); initSeat(); } else if (NAME == "xdg_wm_base") { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 6, id)); m_waylandState.xdg = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &xdg_wm_base_interface, 6)); initShell(); } else if (NAME == "wl_compositor") { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 6, id)); m_waylandState.compositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wl_compositor_interface, 6)); } else if (NAME == "wl_shm") { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.shm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wl_shm_interface, 1)); } else if (NAME == "wp_viewporter") { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.viewporter = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wp_viewporter_interface, 1)); } else if (NAME == wp_fractional_scale_manager_v1_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.fractional = makeShared( (wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wp_fractional_scale_manager_v1_interface, 1)); } else if (NAME == wp_cursor_shape_manager_v1_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.cursorShapeMgr = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wp_cursor_shape_manager_v1_interface, 1)); } else if (NAME == zwp_text_input_manager_v3_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.textInputManager = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &zwp_text_input_manager_v3_interface, 1)); } else if (NAME == zwlr_layer_shell_v1_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 5, id)); m_waylandState.layerShell = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &zwlr_layer_shell_v1_interface, 5)); } else if (NAME == wp_linux_drm_syncobj_manager_v1_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.syncobj = makeShared( (wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wp_linux_drm_syncobj_manager_v1_interface, 1)); } else if (NAME == "zwp_linux_dmabuf_v1") { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 4, id)); m_waylandState.dmabuf = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &zwp_linux_dmabuf_v1_interface, 4)); if (!initDmabuf()) { g_logger->log(HT_LOG_ERROR, "Wayland platform cannot start: zwp_linux_dmabuf_v1 init failed"); m_waylandState.dmabufFailed = true; } } else if (NAME == wl_output_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 4, id)); auto newOutput = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wl_output_interface, 4), id); m_outputs.emplace_back(newOutput); wl_display_roundtrip(m_waylandState.display); // sync display. We emit a new event where the user expects full data. this is not a common event, sync is ok. if (m_waylandState.initialized) g_backend->m_events.outputAdded.emit(newOutput); } else if (NAME == ext_session_lock_manager_v1_interface.name) { TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); m_waylandState.sessionLock = makeShared( (wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &ext_session_lock_manager_v1_interface, 1)); } }); m_waylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t id) { TRACE(g_logger->log(HT_LOG_TRACE, "Global {} removed", id)); if (m_sessionLockState) m_sessionLockState->onOutputRemoved(id); auto outputIt = std::ranges::find_if(m_outputs, [id](const auto& other) { return other->m_id == id; }); if (outputIt != m_outputs.end()) { (*outputIt)->m_events.removed.emit(); m_outputs.erase(outputIt); } }); wl_display_roundtrip(m_waylandState.display); if (!m_waylandState.xdg || !m_waylandState.compositor || !m_waylandState.seat || !m_waylandState.dmabuf || m_waylandState.dmabufFailed || !m_waylandState.shm) { g_logger->log(HT_LOG_ERROR, "Wayland platform cannot start: Missing protocols"); return false; } if (m_waylandState.textInputManager) initIM(); dispatchEvents(); m_waylandState.initialized = true; for (const auto& o : m_outputs) { g_backend->m_events.outputAdded.emit(o); } return true; } CWaylandPlatform::~CWaylandPlatform() { m_outputs.clear(); if (m_drmState.fd >= 0) close(m_drmState.fd); if (m_waylandState.seatState.xkbState) xkb_state_unref(m_waylandState.seatState.xkbState); if (m_waylandState.seatState.xkbKeymap) xkb_keymap_unref(m_waylandState.seatState.xkbKeymap); if (m_waylandState.seatState.xkbContext) xkb_context_unref(m_waylandState.seatState.xkbContext); const auto DPY = m_waylandState.display; m_waylandState = {}; if (DPY) wl_display_disconnect(DPY); } bool CWaylandPlatform::dispatchEvents() { wl_display_flush(m_waylandState.display); if (wl_display_prepare_read(m_waylandState.display) == 0) { wl_display_read_events(m_waylandState.display); wl_display_dispatch_pending(m_waylandState.display); } else wl_display_dispatch(m_waylandState.display); int ret = 0; do { ret = wl_display_dispatch_pending(m_waylandState.display); wl_display_flush(m_waylandState.display); } while (ret > 0); return true; } SP CWaylandPlatform::windowForSurf(wl_proxy* proxy) { for (const auto& w : m_windows) { if (w->m_waylandState.surface && w->m_waylandState.surface->resource() == proxy) return w.lock(); for (const auto& p : w->m_popups) { if (!p) continue; auto pp = reinterpretPointerCast(p.lock()); if (pp->m_waylandState.surface && pp->m_waylandState.surface->resource() == proxy) return pp; } } for (const auto& w : m_layers) { if (w->m_waylandState.surface && w->m_waylandState.surface->resource() == proxy) return w.lock(); for (const auto& p : w->m_popups) { if (!p) continue; auto pp = reinterpretPointerCast(p.lock()); if (pp->m_waylandState.surface && pp->m_waylandState.surface->resource() == proxy) return pp; } } if (m_sessionLockState) { for (const auto& w : m_sessionLockState->m_lockSurfaces) { if (w->m_waylandState.surface && w->m_waylandState.surface->resource() == proxy) return w.lock(); } } return nullptr; } WP CWaylandPlatform::outputForHandle(uint32_t handle) { for (const auto& o : m_outputs) { if (o->m_id == handle) return o; } return SP{}; } void CWaylandPlatform::initIM() { m_waylandState.textInput = makeShared(m_waylandState.textInputManager->sendGetTextInput(m_waylandState.seat->resource())); m_waylandState.textInput->setPreeditString([this](CCZwpTextInputV3* r, const char* s, int32_t begin, int32_t end) { m_waylandState.imState.preeditBegin = begin; m_waylandState.imState.preeditEnd = end; m_waylandState.imState.preeditString = s; }); m_waylandState.textInput->setDeleteSurroundingText([this](CCZwpTextInputV3* r, uint32_t bef, uint32_t aft) { m_waylandState.imState.deleteAfter = aft; m_waylandState.imState.deleteBefore = bef; }); m_waylandState.textInput->setCommitString([this](CCZwpTextInputV3* r, const char* s) { m_waylandState.imState.commitString = s; }); m_waylandState.textInput->setDone([this](CCZwpTextInputV3* r, uint32_t serial) { if (!m_currentWindow || !m_currentWindow->m_keyboardFocus) return; // FIXME: this is incomplete. Very incomplete, but works for CJK IMEs just fine. const auto NEW_STR = m_waylandState.imState.commitString; if (NEW_STR.empty()) { m_currentWindow->m_keyboardFocus->imCommitNewText(m_waylandState.imState.preeditString); return; } m_waylandState.imState = {}; m_currentWindow->m_keyboardFocus->imCommitNewText(NEW_STR); m_currentWindow->m_keyboardFocus->imApplyText(); }); } void CWaylandPlatform::initSeat() { m_waylandState.seat->setCapabilities([this](CCWlSeat* r, wl_seat_capability cap) { const bool HAS_KEYBOARD = ((uint32_t)cap) & WL_SEAT_CAPABILITY_KEYBOARD; const bool HAS_POINTER = ((uint32_t)cap) & WL_SEAT_CAPABILITY_POINTER; if (HAS_KEYBOARD && !m_waylandState.keyboard) { m_waylandState.keyboard = makeShared(m_waylandState.seat->sendGetKeyboard()); m_waylandState.keyboard->setKeymap([this](CCWlKeyboard*, wl_keyboard_keymap_format format, int32_t fd, uint32_t size) { if (!m_waylandState.seatState.xkbContext) return; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { g_logger->log(HT_LOG_ERROR, "wayland: couldn't recognize keymap"); return; } const char* buf = (const char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) { g_logger->log(HT_LOG_ERROR, "wayland: failed to mmap xkb keymap: {}", errno); return; } m_waylandState.seatState.xkbKeymap = xkb_keymap_new_from_buffer(m_waylandState.seatState.xkbContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap((void*)buf, size); close(fd); if (!m_waylandState.seatState.xkbKeymap) { g_logger->log(HT_LOG_ERROR, "wayland: failed to compile xkb keymap"); return; } m_waylandState.seatState.xkbState = xkb_state_new(m_waylandState.seatState.xkbKeymap); if (!m_waylandState.seatState.xkbState) { g_logger->log(HT_LOG_ERROR, "wayland: failed to create xkb state"); return; } const auto PCOMOPOSETABLE = xkb_compose_table_new_from_locale(m_waylandState.seatState.xkbContext, setlocale(LC_CTYPE, nullptr), XKB_COMPOSE_COMPILE_NO_FLAGS); if (!PCOMOPOSETABLE) { g_logger->log(HT_LOG_ERROR, "wayland: failed to create xkb compose table"); return; } m_waylandState.seatState.xkbComposeState = xkb_compose_state_new(PCOMOPOSETABLE, XKB_COMPOSE_STATE_NO_FLAGS); }); m_waylandState.keyboard->setModifiers([this](CCWlKeyboard* r, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { if (!m_waylandState.seatState.xkbContext) return; if (group != m_waylandState.seatState.currentLayer) m_waylandState.seatState.currentLayer = group; xkb_state_update_mask(m_waylandState.seatState.xkbState, mods_depressed, mods_latched, mods_locked, 0, 0, group); m_currentMods = 0; const auto XKB_MASK = mods_depressed | mods_latched | mods_locked; #define GET_MOD_STATE(xkb, ht) \ { \ auto idx = xkb_map_mod_get_index(m_waylandState.seatState.xkbKeymap, xkb); \ m_currentMods |= (XKB_MASK & (1 << idx)) ? ht : 0; \ } GET_MOD_STATE(XKB_MOD_NAME_SHIFT, Input::HT_MODIFIER_SHIFT); GET_MOD_STATE(XKB_MOD_NAME_CAPS, Input::HT_MODIFIER_CAPS); GET_MOD_STATE(XKB_MOD_NAME_CTRL, Input::HT_MODIFIER_CTRL); GET_MOD_STATE(XKB_MOD_NAME_ALT, Input::HT_MODIFIER_ALT); GET_MOD_STATE(XKB_MOD_NAME_NUM, Input::HT_MODIFIER_MOD2); GET_MOD_STATE("Mod3", Input::HT_MODIFIER_MOD3); GET_MOD_STATE(XKB_MOD_NAME_LOGO, Input::HT_MODIFIER_META); GET_MOD_STATE("Mod5", Input::HT_MODIFIER_MOD5); #undef GET_MOD_STATE }); m_waylandState.keyboard->setKey([this](CCWlKeyboard* r, uint32_t serial, uint32_t time, uint32_t key, wl_keyboard_key_state state) { // onKey(key, state == WL_KEYBOARD_KEY_STATE_PRESSED); }); m_waylandState.keyboard->setRepeatInfo([this](CCWlKeyboard* r, uint32_t rate, uint32_t delay) { // m_waylandState.seatState.repeatRate = rate; m_waylandState.seatState.repeatDelay = delay; }); } else if (!HAS_KEYBOARD && m_waylandState.keyboard) m_waylandState.keyboard.reset(); if (HAS_POINTER && !m_waylandState.pointer) { m_waylandState.pointer = makeShared(m_waylandState.seat->sendGetPointer()); m_waylandState.pointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) { auto w = windowForSurf(surf); if (!w) return; Vector2D local = {wl_fixed_to_double(x), wl_fixed_to_double(y)}; w->mouseEnter(local); m_currentWindow = w; m_lastEnterSerial = serial; m_currentMods = 0; setCursor(HT_POINTER_ARROW); m_waylandState.seatState.pressedKeys.clear(); stopRepeatTimer(); }); m_waylandState.pointer->setLeave([this](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { auto w = windowForSurf(surf); if (!w) return; w->mouseLeave(); m_currentWindow.reset(); m_currentMods = 0; m_waylandState.seatState.pressedKeys.clear(); stopRepeatTimer(); }); m_waylandState.pointer->setMotion([this](CCWlPointer* r, uint32_t time, wl_fixed_t x, wl_fixed_t y) { if (!m_currentWindow) return; Vector2D local = {wl_fixed_to_double(x), wl_fixed_to_double(y)}; m_currentWindow->mouseMove(local); }); m_waylandState.pointer->setButton([this](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) { if (!m_currentWindow) return; m_currentWindow->mouseButton(Input::buttonFromWayland(button), state == WL_POINTER_BUTTON_STATE_PRESSED); }); m_waylandState.pointer->setAxis([this](CCWlPointer* r, uint32_t serial, wl_pointer_axis axis, wl_fixed_t delta) { if (!m_currentWindow) return; m_currentWindow->mouseAxis(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ? Input::AXIS_AXIS_HORIZONTAL : Input::AXIS_AXIS_VERTICAL, wl_fixed_to_double(delta)); }); m_waylandState.cursorShapeDev = makeShared(m_waylandState.cursorShapeMgr->sendGetPointer(m_waylandState.pointer->resource())); } else if (!HAS_POINTER && m_waylandState.pointer) { m_waylandState.pointer.reset(); m_waylandState.cursorShapeDev.reset(); } }); } void CWaylandPlatform::initShell() { m_waylandState.xdg->setPing([](CCXdgWmBase* r, uint32_t serial) { r->sendPong(serial); }); } bool CWaylandPlatform::initDmabuf() { m_waylandState.dmabufFeedback = makeShared(m_waylandState.dmabuf->sendGetDefaultFeedback()); if (!m_waylandState.dmabufFeedback) { g_logger->log(HT_LOG_ERROR, "initDmabuf: failed to get default feedback"); return false; } m_waylandState.dmabufFeedback->setDone([this](CCZwpLinuxDmabufFeedbackV1* r) { // no-op g_logger->log(HT_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got done"); }); m_waylandState.dmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* deviceArr) { g_logger->log(HT_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got main device"); dev_t device; ASSERT(deviceArr->size == sizeof(device)); memcpy(&device, deviceArr->data, sizeof(device)); drmDevice* drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { g_logger->log(HT_LOG_ERROR, "zwp_linux_dmabuf_v1: drmGetDeviceFromDevId failed"); return; } const char* name = nullptr; if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) name = drmDev->nodes[DRM_NODE_RENDER]; else { // Likely a split display/render setup. Pick the primary node and hope // Mesa will open the right render node under-the-hood. ASSERT(drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)); name = drmDev->nodes[DRM_NODE_PRIMARY]; g_logger->log(HT_LOG_WARNING, "zwp_linux_dmabuf_v1: DRM device has no render node, using primary."); } if (!name) { g_logger->log(HT_LOG_ERROR, "zwp_linux_dmabuf_v1: no node name"); return; } m_drmState.nodeName = name; drmFreeDevice(&drmDev); g_logger->log(HT_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got node {}", m_drmState.nodeName); }); m_waylandState.dmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int32_t fd, uint32_t size) { #pragma pack(push, 1) struct wlDrmFormatMarshalled { uint32_t drmFormat; char pad[4]; uint64_t modifier; }; #pragma pack(pop) static_assert(sizeof(wlDrmFormatMarshalled) == 16); auto formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (formatTable == MAP_FAILED) { g_logger->log(HT_LOG_ERROR, "zwp_linux_dmabuf_v1: Failed to mmap the format table"); return; } const auto FORMATS = (wlDrmFormatMarshalled*)formatTable; for (size_t i = 0; i < size / 16; ++i) { auto& fmt = FORMATS[i]; auto modName = drmGetFormatModifierName(fmt.modifier); g_logger->log(HT_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got format {} with modifier {}", fourccToName(fmt.drmFormat), modName ? modName : "UNKNOWN"); free(modName); auto it = std::ranges::find_if(m_dmabufFormats, [&fmt](const auto& e) { return e.drmFormat == fmt.drmFormat; }); if (it == m_dmabufFormats.end()) { m_dmabufFormats.emplace_back(Aquamarine::SDRMFormat{.drmFormat = fmt.drmFormat, .modifiers = {fmt.modifier}}); continue; } it->modifiers.emplace_back(fmt.modifier); } munmap(formatTable, size); }); wl_display_roundtrip(m_waylandState.display); if (!m_drmState.nodeName.empty()) { m_drmState.fd = open(m_drmState.nodeName.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC); if (m_drmState.fd < 0) { g_logger->log(HT_LOG_ERROR, "zwp_linux_dmabuf_v1: Failed to open node {}", m_drmState.nodeName); return false; } g_logger->log(HT_LOG_DEBUG, "zwp_linux_dmabuf_v1: opened node {} with fd {}", m_drmState.nodeName, m_drmState.fd); } m_allocator = Aquamarine::CGBMAllocator::create(m_drmState.fd, g_backend->m_aqBackend); auto nullBackend = reinterpretPointerCast(g_backend->m_aqBackend->getImplementations().at(0)); nullBackend->setFormats(m_dmabufFormats); return true; } void CWaylandPlatform::setCursor(ePointerShape shape) { if (!m_waylandState.cursorShapeDev) return; wpCursorShapeDeviceV1Shape wlShape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; switch (shape) { case HT_POINTER_ARROW: break; case HT_POINTER_POINTER: wlShape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; break; case HT_POINTER_TEXT: wlShape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; break; default: break; } m_waylandState.cursorShapeDev->sendSetShape(m_lastEnterSerial, wlShape); } void CWaylandPlatform::onKey(uint32_t keycode, bool state) { const auto HAS_IN_VEC = std::ranges::contains(m_waylandState.seatState.pressedKeys, keycode); if (state && HAS_IN_VEC) { g_logger->log(HT_LOG_ERROR, "Invalid key down event (key already pressed?)"); return; } else if (!state && !HAS_IN_VEC) { g_logger->log(HT_LOG_ERROR, "Invalid key up event (stray release event?)"); return; } if (state) m_waylandState.seatState.pressedKeys.emplace_back(keycode); else std::erase(m_waylandState.seatState.pressedKeys, keycode); if (!m_currentWindow) return; Input::SKeyboardKeyEvent e; e.down = state; e.modMask = m_currentMods; if (state) { const auto SYM = xkb_state_key_get_one_sym(m_waylandState.seatState.xkbState, keycode + 8); if (SYM == XKB_KEY_Left || SYM == XKB_KEY_Right || SYM == XKB_KEY_Up || SYM == XKB_KEY_Down) { // skip compose e.xkbKeysym = SYM; m_currentWindow->keyboardKey(e); m_waylandState.seatState.repeatKeyEvent = e; startRepeatTimer(); return; } enum xkb_compose_status composeStatus = XKB_COMPOSE_NOTHING; if (m_waylandState.seatState.xkbComposeState) { xkb_compose_state_feed(m_waylandState.seatState.xkbComposeState, SYM); composeStatus = xkb_compose_state_get_status(m_waylandState.seatState.xkbComposeState); } const bool COMPOSED = composeStatus == XKB_COMPOSE_COMPOSED; char buf[16] = {0}; int len = COMPOSED ? xkb_compose_state_get_utf8(m_waylandState.seatState.xkbComposeState, buf, sizeof(buf)) /* nullbyte */ + 1 : xkb_keysym_to_utf8(SYM, buf, sizeof(buf)) /* already includes a nullbyte */; if (len > 1) { e.xkbKeysym = SYM; e.utf8 = std::string{buf, sc(len - 1)}; m_currentWindow->keyboardKey(e); m_waylandState.seatState.repeatKeyEvent = e; } startRepeatTimer(); return; } else if (m_waylandState.seatState.xkbComposeState && xkb_compose_state_get_status(m_waylandState.seatState.xkbComposeState) == XKB_COMPOSE_COMPOSED) xkb_compose_state_reset(m_waylandState.seatState.xkbComposeState); m_waylandState.seatState.repeatKeyEvent = e; m_currentWindow->keyboardKey(e); stopRepeatTimer(); } void CWaylandPlatform::onRepeatTimerFire() { if (!m_currentWindow) return; m_currentWindow->keyboardKey(m_waylandState.seatState.repeatKeyEvent); // add a repeat timer m_waylandState.seatState.repeatTimer = g_backend->addTimer(std::chrono::milliseconds(1000 / m_waylandState.seatState.repeatRate), [this](ASP self, void*) { onRepeatTimerFire(); }, nullptr); } void CWaylandPlatform::startRepeatTimer() { if (m_waylandState.seatState.repeatDelay == 0 || m_waylandState.seatState.repeatRate == 0) return; if (m_waylandState.seatState.repeatTimer) m_waylandState.seatState.repeatTimer->cancel(); m_waylandState.seatState.repeatKeyEvent.repeat = true; m_waylandState.seatState.repeatTimer = g_backend->addTimer(std::chrono::milliseconds(m_waylandState.seatState.repeatDelay), [this](ASP self, void*) { onRepeatTimerFire(); }, nullptr); } void CWaylandPlatform::stopRepeatTimer() { if (m_waylandState.seatState.repeatTimer) m_waylandState.seatState.repeatTimer->cancel(); m_waylandState.seatState.repeatTimer.reset(); } SP CWaylandPlatform::aquireSessionLock() { if (m_sessionLockState) return m_sessionLockState.lock(); auto sessionLock = makeShared(makeShared(m_waylandState.sessionLock->sendLock())); // roundtrip in case the compositor sends `finished` right away wl_display_roundtrip(m_waylandState.display); m_sessionLockState = sessionLock; return sessionLock; } hyprwm-hyprtoolkit-71515e8/src/core/platforms/WaylandPlatform.hpp000066400000000000000000000124671513450241200252610ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../../helpers/Memory.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Hyprtoolkit { typedef std::function FIdleCallback; class CWaylandWindow; class IWaylandWindow; class CWaylandLayer; class CWaylandOutput; class CWaylandSessionLockState; class CWaylandPlatform { public: CWaylandPlatform() = default; ~CWaylandPlatform(); bool attempt(); void initSeat(); void initShell(); bool initDmabuf(); void initIM(); void setCursor(ePointerShape shape); bool dispatchEvents(); SP windowForSurf(wl_proxy* proxy); WP outputForHandle(uint32_t handle); void onKey(uint32_t keycode, bool state); void startRepeatTimer(); void stopRepeatTimer(); void onRepeatTimerFire(); SP aquireSessionLock(); // dmabuf formats std::vector m_dmabufFormats; SP m_allocator; struct { wl_display* display = nullptr; // hw-s types Hyprutils::Memory::CSharedPointer registry; Hyprutils::Memory::CSharedPointer seat; Hyprutils::Memory::CSharedPointer shm; Hyprutils::Memory::CSharedPointer xdg; Hyprutils::Memory::CSharedPointer compositor; Hyprutils::Memory::CSharedPointer dmabuf; Hyprutils::Memory::CSharedPointer dmabufFeedback; Hyprutils::Memory::CSharedPointer fractional; Hyprutils::Memory::CSharedPointer viewporter; Hyprutils::Memory::CSharedPointer keyboard; Hyprutils::Memory::CSharedPointer pointer; Hyprutils::Memory::CSharedPointer cursorShapeMgr; Hyprutils::Memory::CSharedPointer cursorShapeDev; Hyprutils::Memory::CSharedPointer textInputManager; Hyprutils::Memory::CSharedPointer textInput; Hyprutils::Memory::CSharedPointer layerShell; Hyprutils::Memory::CSharedPointer syncobj; Hyprutils::Memory::CSharedPointer sessionLock; // control bool initialized = false; bool dmabufFailed = false; struct { xkb_context* xkbContext = nullptr; xkb_keymap* xkbKeymap = nullptr; xkb_state* xkbState = nullptr; xkb_compose_state* xkbComposeState = nullptr; uint32_t currentLayer = 0; uint32_t repeatRate = 10, repeatDelay = 500; std::vector pressedKeys; Input::SKeyboardKeyEvent repeatKeyEvent; ASP repeatTimer; } seatState; struct { bool entered = false, enabled = false; std::string preeditString; int preeditBegin = 0, preeditEnd = 0; std::string commitString; size_t deleteBefore = 0, deleteAfter = 0; std::string originalString; } imState; } m_waylandState; struct { int fd = -1; std::string nodeName = ""; } m_drmState; std::vector> m_outputs; std::vector> m_windows; std::vector> m_layers; WP m_currentWindow; uint32_t m_currentMods = 0; // HT modifiers, not xkb uint32_t m_lastEnterSerial = 0; WP m_sessionLockState; }; inline UP g_waylandPlatform; } hyprwm-hyprtoolkit-71515e8/src/element/000077500000000000000000000000001513450241200201245ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/Element.cpp000066400000000000000000000175411513450241200222310ustar00rootroot00000000000000#include "Element.hpp" #include #include #include "../helpers/Memory.hpp" #include "../window/ToolkitWindow.hpp" #include "../layout/Positioner.hpp" #include using namespace Hyprtoolkit; using namespace Hyprutils::Math; IElement::IElement() { impl = UP(new SElementInternalData()); impl->m_externalEvents.mouseEnter.listenStatic([this](Vector2D local) { if (!impl->userRequestedMouseInput) return; if (impl->userFns.mouseEnter) impl->userFns.mouseEnter(local); }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { if (!impl->userRequestedMouseInput) return; if (impl->userFns.mouseLeave) impl->userFns.mouseLeave(); }); impl->m_externalEvents.mouseMove.listenStatic([this](Vector2D local) { if (!impl->userRequestedMouseInput) return; if (impl->userFns.mouseMove) impl->userFns.mouseMove(local); }); impl->m_externalEvents.mouseButton.listenStatic([this](Input::eMouseButton button, bool down) { if (!impl->userRequestedMouseInput) return; if (impl->userFns.mouseButton) impl->userFns.mouseButton(button, down); }); impl->m_externalEvents.mouseAxis.listenStatic([this](Input::eAxisAxis axis, bool down) { if (!impl->userRequestedMouseInput) return; if (impl->userFns.mouseAxis) impl->userFns.mouseAxis(axis, down); }); } IElement::~IElement() { impl.reset(); } void IElement::setPositionMode(ePositionMode mode) { impl->positionMode = mode; if (impl->window) impl->window->scheduleReposition(impl->self); } void IElement::setPositionFlag(ePositionFlag flag, bool set) { if (set) impl->positionFlags |= flag; else impl->positionFlags &= ~flag; if (impl->window) impl->window->scheduleReposition(impl->self); } void IElement::setAbsolutePosition(const Hyprutils::Math::Vector2D& offset) { impl->absoluteOffset = offset; if (impl->window) impl->window->scheduleReposition(impl->self); } void IElement::setTooltip(std::string&& x) { impl->tooltip = std::move(x); impl->hasTooltip = !impl->tooltip.empty(); } std::optional IElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return std::nullopt; } std::optional IElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return std::nullopt; } std::optional IElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return std::nullopt; } void IElement::setGrow(bool grow) { impl->growH = grow; impl->growV = grow; } void IElement::setGrow(bool growH, bool growV) { impl->growH = growH; impl->growV = growV; } void IElement::addChild(Hyprutils::Memory::CSharedPointer child) { if (std::ranges::find(impl->children, child) != impl->children.end()) return; child->impl->parent = impl->self.lock(); child->impl->window = impl->window; child->impl->breadthfirst([w = impl->window.lock()](SP e) { e->impl->setWindow(w); }); impl->children.emplace_back(child); if (impl->window) impl->window->scheduleReposition(child); } void IElement::removeChild(Hyprutils::Memory::CSharedPointer child) { if (std::ranges::find(impl->children, child) == impl->children.end()) return; std::erase(impl->children, child); child->impl->parent.reset(); child->impl->window.reset(); child->impl->breadthfirst([](SP e) { e->impl->setWindow(nullptr); }); if (impl->window) impl->window->scheduleReposition(impl->self); } void IElement::clearChildren() { for (auto& c : impl->children) { c->impl->parent.reset(); c->impl->window.reset(); } impl->children.clear(); } bool IElement::acceptsMouseInput() { return impl->userRequestedMouseInput || impl->hasTooltip; } ePointerShape IElement::pointerShape() { return HT_POINTER_ARROW; } std::function IElement::pointerShapeFn() { return nullptr; } bool IElement::alwaysGetMouseInput() { return false; } void IElement::setMargin(float thick) { impl->margin = thick; } void IElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { impl->setPosition(box); if (impl->userFns.repositioned) impl->userFns.repositioned(); } void IElement::recheckColor() { ; } bool IElement::acceptsKeyboardInput() { return false; } void IElement::imCommitNewText(const std::string& text) { ; } void IElement::imApplyText() { ; } void IElement::setReceivesMouse(bool x) { impl->userRequestedMouseInput = true; } void IElement::setMouseEnter(std::function&& fn) { impl->userFns.mouseEnter = std::move(fn); } void IElement::setMouseLeave(std::function&& fn) { impl->userFns.mouseLeave = std::move(fn); } void IElement::setMouseMove(std::function&& fn) { impl->userFns.mouseMove = std::move(fn); } void IElement::setMouseButton(std::function&& fn) { impl->userFns.mouseButton = std::move(fn); } void IElement::setMouseAxis(std::function&& fn) { impl->userFns.mouseAxis = std::move(fn); } void IElement::setRepositioned(std::function&& fn) { impl->userFns.repositioned = std::move(fn); } void IElement::setGrouped(bool grouped) { impl->grouped = grouped; } Vector2D IElement::posFromParent() { if (!impl->parent) return impl->position.pos(); return impl->position.pos() - impl->parent->impl->position.pos(); } bool IElement::positioningDependsOnChild() { return false; } CBox IElement::opaqueBox() { return {}; } void IElement::forceReposition() { g_positioner->repositionNeeded(impl->self.lock(), true); } void SElementInternalData::setPosition(const CBox& box) { position = box; if (margin > 0) position.expand(-margin); } void SElementInternalData::bfHelper(std::vector> elements, const std::function)>& fn) { for (const auto& e : elements) { fn(e); } std::vector> els; for (const auto& e : elements) { for (const auto& c : e->impl->children) { els.emplace_back(c); } } if (!els.empty()) bfHelper(els, fn); } void SElementInternalData::breadthfirst(const std::function)>& fn) { fn(self.lock()); std::vector> els = children; bfHelper(els, fn); } void SElementInternalData::setWindow(SP w) { window = w; if (w) w->scheduleReposition(self); } void SElementInternalData::damageEntire() { if (!window) return; window->damage(position.copy().expand(2)); } void SElementInternalData::setFailedPositioning(bool set) { breadthfirst([set](SP e) { e->impl->failedPositioning = set; }); } Vector2D SElementInternalData::maxChildSize(const Vector2D& parent) { Vector2D max; for (const auto& e : children) { auto size = e->preferredSize(parent); if (!size) size = e->minimumSize(parent); if (!size) continue; max.x = std::max(max.x, size->x); max.y = std::max(max.y, size->y); } return max + Vector2D{margin * 2, margin * 2}; } Vector2D SElementInternalData::getPreferredSizeGeneric(const CDynamicSize& size, const Vector2D& parent) { auto s = size.calculate(parent); if (s.x != -1 && s.y != -1) return s; auto max = maxChildSize(parent - Vector2D{margin * 2, margin * 2}); if (s.x == -1) s.x = max.x; if (s.y == -1) s.y = max.y; return s; } hyprwm-hyprtoolkit-71515e8/src/element/Element.hpp000066400000000000000000000073441513450241200222360ustar00rootroot00000000000000#pragma once #include #include #include #include "../helpers/Memory.hpp" #include "../core/Input.hpp" #include namespace Hyprtoolkit { class IToolkitWindow; struct SPositionerData; struct SToolkitWindowData; class CDynamicSize; struct SElementInternalData { Hyprutils::Memory::CWeakPointer self; Hyprutils::Memory::CWeakPointer window; Hyprutils::Math::CBox position; UP positionerData; UP toolkitWindowData; std::vector> children; IElement::ePositionMode positionMode = IElement::HT_POSITION_AUTO; uint8_t positionFlags = 0; Hyprutils::Math::Vector2D absoluteOffset; bool growV = false; bool growH = false; float margin = 0; bool userRequestedMouseInput = false; bool grouped = false; // tooltip std::string tooltip = ""; bool hasTooltip = false; // rendering: clip children to parent box bool clipChildren = false; WP parent; bool failedPositioning = false; struct { Hyprutils::Signal::CSignalT mouseEnter; // local coords Hyprutils::Signal::CSignalT mouseMove; // local coords Hyprutils::Signal::CSignalT mouseButton; Hyprutils::Signal::CSignalT<> mouseLeave; Hyprutils::Signal::CSignalT mouseAxis; Hyprutils::Signal::CSignalT key; Hyprutils::Signal::CSignalT<> keyboardEnter; Hyprutils::Signal::CSignalT<> keyboardLeave; } m_externalEvents; struct { std::function mouseEnter; std::function mouseLeave; std::function mouseMove; std::function mouseButton; std::function mouseAxis; std::function repositioned; } userFns; // void bfHelper(std::vector> elements, const std::function)>& fn); void breadthfirst(const std::function)>& fn); void setWindow(SP w); void damageEntire(); void setPosition(const Hyprutils::Math::CBox& box); void setFailedPositioning(bool set); Hyprutils::Math::Vector2D maxChildSize(const Hyprutils::Math::Vector2D& parent); Hyprutils::Math::Vector2D getPreferredSizeGeneric(const CDynamicSize& size, const Hyprutils::Math::Vector2D& parent); }; } hyprwm-hyprtoolkit-71515e8/src/element/button/000077500000000000000000000000001513450241200214375ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/button/Builder.cpp000066400000000000000000000032311513450241200235300ustar00rootroot00000000000000#include "Button.hpp" using namespace Hyprtoolkit; SP CButtonBuilder::begin() { SP p = SP(new CButtonBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CButtonBuilder::label(std::string&& s) { m_data->label = std::move(s); return m_self.lock(); } SP CButtonBuilder::noBorder(bool x) { m_data->noBorder = x; return m_self.lock(); } SP CButtonBuilder::noBg(bool x) { m_data->noBg = x; return m_self.lock(); } SP CButtonBuilder::fontFamily(std::string&& x) { m_data->fontFamily = std::move(x); return m_self.lock(); } SP CButtonBuilder::fontSize(CFontSize&& x) { m_data->fontSize = std::move(x); return m_self.lock(); } SP CButtonBuilder::alignText(eFontAlignment x) { m_data->alignText = x; return m_self.lock(); } SP CButtonBuilder::onMainClick(std::function)>&& f) { m_data->onMainClick = std::move(f); return m_self.lock(); } SP CButtonBuilder::onRightClick(std::function)>&& f) { m_data->onRightClick = std::move(f); return m_self.lock(); } SP CButtonBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CButtonBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CButtonElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/button/Button.cpp000066400000000000000000000151151513450241200234210ustar00rootroot00000000000000#include "Button.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CButtonElement::create(const SButtonData& data) { auto p = SP(new CButtonElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CButtonElement::CButtonElement(const SButtonData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; m_impl->background = CRectangleBuilder::begin() ->color([nobg = m_impl->data.noBg] { if (nobg) return CHyprColor{g_palette->m_colors.base.asRGB(), 0.F}; return g_palette->m_colors.base; }) ->rounding(g_palette->m_vars.smallRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(data.noBorder ? 0 : 1) ->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_impl->label = CTextBuilder::begin() ->text(std::string{data.label}) ->fontSize(CFontSize{data.fontSize}) ->fontFamily(std::string{data.fontFamily}) ->color([] { return g_palette->m_colors.text; }) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1.F, 1.F}}) ->callback([this] { m_impl->labelChanged = true; if (impl->window) impl->window->scheduleReposition(impl->self); }) ->noEllipsize(true) ->commence(); m_impl->label->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->label->setPositionFlag( m_impl->data.alignText == HT_FONT_ALIGN_CENTER ? HT_POSITION_FLAG_CENTER : (m_impl->data.alignText == HT_FONT_ALIGN_RIGHT ? HT_POSITION_FLAG_RIGHT : HT_POSITION_FLAG_LEFT), true); m_impl->label->setPositionFlag(HT_POSITION_FLAG_VCENTER, true); addChild(m_impl->background); m_impl->background->addChild(m_impl->label); m_impl->label->setMargin(2); impl->m_externalEvents.mouseEnter.listenStatic([this](const Vector2D& pos) { m_impl->background ->rebuild() // ->color([nb = m_impl->data.noBorder, nobg = m_impl->data.noBg] { if (nobg) return g_palette->m_colors.base.brighten(0.05F); return g_palette->m_colors.base.brighten(nb ? 0.3F : 0.11F); }) ->borderColor([] { return g_palette->m_colors.accent; }) ->commence(); }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { m_impl->background ->rebuild() // ->color([nobg = m_impl->data.noBg] { if (nobg) return CHyprColor{g_palette->m_colors.base.asRGB(), 0.F}; return g_palette->m_colors.base; }) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->commence(); }); impl->m_externalEvents.mouseButton.listenStatic([this](const Input::eMouseButton button, bool down) { if (!down) return; if (button == Input::MOUSE_BUTTON_RIGHT) { if (m_impl->data.onRightClick) m_impl->data.onRightClick(m_impl->self.lock()); } else if (button == Input::MOUSE_BUTTON_LEFT) { if (m_impl->data.onMainClick) m_impl->data.onMainClick(m_impl->self.lock()); } }); impl->grouped = true; } void CButtonElement::paint() { ; } void CButtonElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CButtonElement::rebuild() { auto p = SP(new CButtonBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CButtonElement::replaceData(const SButtonData& data) { m_impl->data = data; m_impl->label->rebuild()->text(std::string{data.label})->commence(); m_impl->label->setPositionFlag(HT_POSITION_FLAG_ALL, false); m_impl->label->setPositionFlag( m_impl->data.alignText == HT_FONT_ALIGN_CENTER ? HT_POSITION_FLAG_CENTER : (m_impl->data.alignText == HT_FONT_ALIGN_RIGHT ? HT_POSITION_FLAG_RIGHT : HT_POSITION_FLAG_LEFT), true); if (impl->window) impl->window->scheduleReposition(impl->self); } Hyprutils::Math::Vector2D CButtonElement::size() { return impl->position.size(); } constexpr double BUTTON_PAD = 5; std::optional CButtonElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->label->preferredSize(parent).value() + Vector2D{BUTTON_PAD * 2, BUTTON_PAD * 2}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } std::optional CButtonElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->label->preferredSize(parent).value() + Vector2D{BUTTON_PAD * 2, BUTTON_PAD * 2}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } std::optional CButtonElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->label->preferredSize(parent).value() + Vector2D{BUTTON_PAD * 2, BUTTON_PAD * 2}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } bool CButtonElement::acceptsMouseInput() { return true; } ePointerShape CButtonElement::pointerShape() { return HT_POINTER_POINTER; } bool CButtonElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/button/Button.hpp000066400000000000000000000027761513450241200234370ustar00rootroot00000000000000#pragma once #include #include #include #include "../../helpers/Memory.hpp" #include "../../core/InternalBackend.hpp" namespace Hyprtoolkit { struct SButtonData { std::string label = "Click me"; bool noBorder = false; bool noBg = false; std::string fontFamily = g_palette ? g_palette->m_vars.fontFamily : "Sans Serif"; CFontSize fontSize = {CFontSize::HT_FONT_TEXT}; eFontAlignment alignText = HT_FONT_ALIGN_CENTER; std::function)> onMainClick; std::function)> onRightClick; CDynamicSize size{CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}; }; struct SButtonImpl { SButtonData data; WP self; SP background; SP label; bool labelChanged = true; }; } hyprwm-hyprtoolkit-71515e8/src/element/checkbox/000077500000000000000000000000001513450241200217125ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/checkbox/Builder.cpp000066400000000000000000000016361513450241200240120ustar00rootroot00000000000000#include "Checkbox.hpp" using namespace Hyprtoolkit; SP CCheckboxBuilder::begin() { SP p = SP(new CCheckboxBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CCheckboxBuilder::onToggled(std::function, bool)>&& f) { m_data->onToggled = std::move(f); return m_self.lock(); } SP CCheckboxBuilder::toggled(bool x) { m_data->toggled = x; return m_self.lock(); } SP CCheckboxBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CCheckboxBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CCheckboxElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/checkbox/Checkbox.cpp000066400000000000000000000122431513450241200241460ustar00rootroot00000000000000#include "Checkbox.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CCheckboxElement::create(const SCheckboxData& data) { auto p = SP(new CCheckboxElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } std::function SCheckboxImpl::getFgColor() { if (data.toggled) return [] { return g_palette->m_colors.accent; }; else { return [] { auto c = g_palette->m_colors.accent; c.a = 0.F; return c; }; } } CCheckboxElement::CCheckboxElement(const SCheckboxData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; m_impl->background = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.base; }) ->rounding(g_palette->m_vars.smallRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(1) ->size(CDynamicSize{CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {14.F, 14.F}}) ->commence(); m_impl->background->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->background->setPositionFlag(HT_POSITION_FLAG_CENTER, true); CHyprColor col = g_palette->m_colors.accent; col.a = m_impl->data.toggled ? 1.F : 0.F; m_impl->foreground = CCheckmarkElement::create(SCheckmarkData{.size = {CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}, .color = [col] { return col; }}); m_impl->foreground->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->foreground->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_impl->background->addChild(m_impl->foreground); addChild(m_impl->background); impl->m_externalEvents.mouseEnter.listenStatic([this](const Vector2D& pos) { m_impl->background ->rebuild() // ->color([] { return g_palette->m_colors.base.brighten(0.11F); }) ->borderColor([] { return g_palette->m_colors.alternateBase.brighten(0.5F); }) ->commence(); m_impl->primedForUp = false; }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { m_impl->background ->rebuild() // ->color([] { return g_palette->m_colors.base; }) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->commence(); m_impl->primedForUp = false; }); impl->m_externalEvents.mouseButton.listenStatic([this](const Input::eMouseButton button, bool down) { if (down) { m_impl->primedForUp = true; return; } if (!m_impl->primedForUp) return; if (button == Input::MOUSE_BUTTON_LEFT) { m_impl->data.toggled = !m_impl->data.toggled; if (m_impl->data.onToggled) m_impl->data.onToggled(m_impl->self.lock(), m_impl->data.toggled); CHyprColor col = g_palette->m_colors.accent; col.a = m_impl->data.toggled ? 1.F : 0.F; *m_impl->foreground->m_color = col; } }); impl->grouped = true; } void CCheckboxElement::paint() { ; } void CCheckboxElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CCheckboxElement::rebuild() { auto p = SP(new CCheckboxBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CCheckboxElement::replaceData(const SCheckboxData& data) { m_impl->data = data; CHyprColor col = g_palette->m_colors.accent; col.a = m_impl->data.toggled ? 1.F : 0.F; *m_impl->foreground->m_color = col; if (impl->window) impl->window->scheduleReposition(impl->self); } Hyprutils::Math::Vector2D CCheckboxElement::size() { return impl->position.size(); } std::optional CCheckboxElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } std::optional CCheckboxElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } std::optional CCheckboxElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } bool CCheckboxElement::acceptsMouseInput() { return true; } ePointerShape CCheckboxElement::pointerShape() { return HT_POINTER_POINTER; } bool CCheckboxElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/checkbox/Checkbox.hpp000066400000000000000000000045441513450241200241600ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "../../helpers/Memory.hpp" #include "../../core/AnimatedVariable.hpp" namespace Hyprtoolkit { struct SCheckmarkData { CDynamicSize size = {CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; colorFn color = [] { return CHyprColor{1.F, 0.F, 0.F}; }; }; class CCheckmarkElement : public IElement { public: static Hyprutils::Memory::CSharedPointer create(const SCheckmarkData& data); virtual ~CCheckmarkElement() = default; private: CCheckmarkElement(const SCheckmarkData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual Hyprutils::Math::Vector2D size(); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); SCheckmarkData m_data; PHLANIMVAR m_color; friend class CCheckboxElement; }; struct SCheckboxData { bool toggled = false; std::function, bool)> onToggled; CDynamicSize size{CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {}}; }; struct SCheckboxImpl { SCheckboxData data; WP self; SP background; SP foreground; bool labelChanged = true; bool primedForUp = false; std::function getFgColor(); }; } hyprwm-hyprtoolkit-71515e8/src/element/checkbox/Checkmark.cpp000066400000000000000000000035011513450241200243050ustar00rootroot00000000000000#include "Checkbox.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CCheckmarkElement::create(const SCheckmarkData& data) { auto p = SP(new CCheckmarkElement(data)); p->impl->self = p; return p; } CCheckmarkElement::CCheckmarkElement(const SCheckmarkData& data) : IElement(), m_data(data) { g_animationManager->createAnimation(data.color(), m_color, g_animationManager->m_animationTree.getConfig("fast")); m_color->setUpdateCallback([this](auto) { impl->damageEntire(); }); m_color->setCallbackOnBegin([this](auto) { impl->damageEntire(); }, false); } void CCheckmarkElement::paint() { g_renderer->renderPolygon(IRenderer::SPolygonRenderData{ .box = impl->position, .color = m_color->value(), .poly = CPolygon::checkmark(), }); } void CCheckmarkElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } Hyprutils::Math::Vector2D CCheckmarkElement::size() { return impl->position.size(); } std::optional CCheckmarkElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } std::optional CCheckmarkElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } std::optional CCheckmarkElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } hyprwm-hyprtoolkit-71515e8/src/element/columnLayout/000077500000000000000000000000001513450241200226175ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/columnLayout/Builder.cpp000066400000000000000000000014161513450241200247130ustar00rootroot00000000000000#include "ColumnLayout.hpp" using namespace Hyprtoolkit; SP CColumnLayoutBuilder::begin() { SP p = SP(new CColumnLayoutBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CColumnLayoutBuilder::gap(size_t x) { m_data->gap = x; return m_self.lock(); } SP CColumnLayoutBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CColumnLayoutBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CColumnLayoutElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/columnLayout/ColumnLayout.cpp000066400000000000000000000160731513450241200257650ustar00rootroot00000000000000#include "ColumnLayout.hpp" #include #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../core/InternalBackend.hpp" #include "../../window/ToolkitWindow.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CColumnLayoutElement::create(const SColumnLayoutData& data) { auto p = SP(new CColumnLayoutElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CColumnLayoutElement::CColumnLayoutElement(const SColumnLayoutData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CColumnLayoutElement::paint() { ; // no-op } SP CColumnLayoutElement::rebuild() { auto p = SP(new CColumnLayoutBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CColumnLayoutElement::replaceData(const SColumnLayoutData& data) { m_impl->data = data; if (impl->window) impl->window->scheduleReposition(impl->self); } // FIXME: de-dup with rowlayout? void CColumnLayoutElement::reposition(const Hyprutils::Math::CBox& sbox, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(sbox); const auto& box = impl->position; const auto C = impl->children; // position children in this layout. size_t usedY = 0; const size_t MAX_Y = (uint64_t)impl->position.size().y; size_t i = 0; std::vector heights; heights.resize(impl->children.size()); for (i = 0; i < C.size(); ++i) { const auto& child = C.at(i); Vector2D cSize = childSize(child); if (cSize == Vector2D{-1, -1}) cSize = {box.w, 1.F}; if (usedY + cSize.y > MAX_Y + 1) { // doesn't fit: try to shrink any previous element if it allows to do so // FIXME: if we resize and fail to fit, it will mess up the layout a bit float needs = (usedY + cSize.y) - (MAX_Y + 1); for (int j = i - 1; j >= 0; --j) { const auto& prevChild = C.at(j); const auto MIN = prevChild->minimumSize(impl->position.size()); if (!MIN.has_value()) { // we can shrink this child to zero if (needs > heights.at(j)) { heights.at(j) -= needs - heights.at(j); needs -= needs - heights.at(j); continue; } heights.at(j) -= needs; needs = 0; break; // done } else if (MIN->y < heights.at(j)) { // we can shrink it a bit if (needs > (heights.at(j) - MIN->y)) { heights.at(j) -= needs - (heights.at(j) - MIN->y); needs -= needs - (heights.at(j) - MIN->y); continue; } heights.at(j) -= needs; needs = 0; break; // done } } if (needs > 0) { // doesn't fit: disable and expand the last if possible child->impl->setFailedPositioning(true); if (i != 0) { // try to expand last child const auto& lastChild = C.at(i - 1); if (lastChild->maximumSize(box.size()) && heights.at(i - 1) + MAX_Y - usedY > lastChild->maximumSize(box.size())->y) continue; // too bad, we'll have a gap heights.at(i - 1) += MAX_Y - usedY; } continue; } else { child->impl->setFailedPositioning(false); heights.at(i) = cSize.y; // recalc usedX cuz we changed stuff usedY = 0; for (const auto& h : heights) { usedY += h + m_impl->data.gap; } continue; } } // can fit: use preferred heights.at(i) = cSize.y; child->impl->setFailedPositioning(false); usedY += cSize.y + m_impl->data.gap; } if (!C.empty()) usedY -= m_impl->data.gap; // check if any element grows and grow if applicable if (usedY < MAX_Y) { for (i = 0; i < C.size(); ++i) { const auto& child = C.at(i); if (!child->impl->growV) continue; heights.at(i) += MAX_Y - usedY; break; } } // widths done: lay out elements size_t currentY = 0; for (i = 0; i < C.size(); ++i) { const auto& child = C.at(i); if (child->impl->failedPositioning) continue; Vector2D cSize = childSize(child); cSize.x = std::clamp(cSize.x, 0.0, impl->position.w); CBox childBox = CBox{box.x + ((box.w - cSize.x) / 2), box.y + currentY, cSize.x, (double)heights.at(i)}; g_positioner->position(child, childBox, Vector2D{box.w, childBox.h + (MAX_Y - usedY) /* this can potentially cause problems... ↓ */}); // This essentially tells each item it can grow, so if we have two // texts next to each other, they might fight for space. FIXME: currentY += childBox.h + m_impl->data.gap; } } Hyprutils::Math::Vector2D CColumnLayoutElement::size() { return impl->position.size(); } Hyprutils::Math::Vector2D CColumnLayoutElement::childSize(Hyprutils::Memory::CSharedPointer child) { if (child->preferredSize(impl->position.size())) return *child->preferredSize(impl->position.size()); else if (child->minimumSize(impl->position.size())) return *child->minimumSize(impl->position.size()); return {-1, -1}; } std::optional CColumnLayoutElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { auto calc = m_impl->data.size.calculate(parent); if (calc.x != -1 && calc.y != -1) return calc; Vector2D max; for (const auto& child : impl->children) { max.x = std::max(childSize(child).x, max.x); max.y += childSize(child).y + m_impl->data.gap; } if (!impl->children.empty()) max.y -= m_impl->data.gap; max.x += impl->margin * 2; max.y += impl->margin * 2; max.x = std::ceil(max.x); max.y = std::ceil(max.y); if (calc.x == -1) calc.x = max.x; if (calc.y == -1) calc.y = max.y; return calc; } std::optional CColumnLayoutElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { Vector2D min; for (const auto& child : impl->children) { min.x = std::max(min.x, childSize(child).x); } return min; } bool CColumnLayoutElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/columnLayout/ColumnLayout.hpp000066400000000000000000000006251513450241200257660ustar00rootroot00000000000000#include #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { struct SColumnLayoutData { CDynamicSize size = CDynamicSize(CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}); size_t gap = 0; }; struct SColumnLayoutImpl { SColumnLayoutData data; WP self; }; } hyprwm-hyprtoolkit-71515e8/src/element/combobox/000077500000000000000000000000001513450241200217345ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/combobox/Builder.cpp000066400000000000000000000020661513450241200240320ustar00rootroot00000000000000#include "Combobox.hpp" using namespace Hyprtoolkit; SP CComboboxBuilder::begin() { SP p = SP(new CComboboxBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CComboboxBuilder::items(std::vector&& x) { m_data->items = std::move(x); return m_self.lock(); } SP CComboboxBuilder::currentItem(size_t x) { m_data->currentItem = x; return m_self.lock(); } SP CComboboxBuilder::onChanged(std::function, size_t)>&& x) { m_data->onChanged = std::move(x); return m_self.lock(); } SP CComboboxBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CComboboxBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CComboboxElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/combobox/Combobox.cpp000066400000000000000000000247321513450241200242200ustar00rootroot00000000000000#include "Combobox.hpp" #include #include #include #include #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../../window/Window.hpp" #include "../Element.hpp" #include "../../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CComboboxElement::create(const SComboboxData& data) { auto p = SP(new CComboboxElement(data)); p->impl->self = p; p->m_impl->self = p; p->init(); return p; } constexpr float INNER_MARG = 4.F; constexpr float HANDLE_SIZE = 12.F; constexpr float DROPDOWN_BUTTON_HEIGHT = 30.F; constexpr float DROPDOWN_BUTTON_PAD = 2.F; constexpr float DROPDOWN_MARGIN_OUTER = 6.F; CComboboxElement::CComboboxElement(const SComboboxData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CComboboxElement::init() { m_impl->layout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 24}})->commence(); m_impl->layout->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_impl->layout->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->layout->setMargin(INNER_MARG); m_impl->label = CTextBuilder::begin() ->text(std::string{m_impl->data.items.at(m_impl->data.currentItem)}) ->color([] { return g_palette->m_colors.text; }) ->callback([this] { if (impl->window) impl->window->scheduleReposition(impl->self); }) ->commence(); m_impl->background = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.base; }) ->rounding(g_palette->m_vars.smallRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(1) ->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_impl->background->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->background->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_impl->background->impl->clipChildren = true; m_impl->handle = CDropdownHandleElement::create(SDropdownHandleData{ .size = {CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {HANDLE_SIZE, HANDLE_SIZE}}, .color = [] { return g_palette->m_colors.text; }, }); m_impl->leftPad = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {INNER_MARG, 1.F}})->commence(); m_impl->rightPad = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {INNER_MARG, 1.F}})->commence(); m_impl->middlePad = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {INNER_MARG, 1.F}})->commence(); m_impl->middlePad->setGrow(true); m_impl->layout->addChild(m_impl->leftPad); m_impl->layout->addChild(m_impl->label); m_impl->layout->addChild(m_impl->middlePad); m_impl->layout->addChild(m_impl->handle); m_impl->layout->addChild(m_impl->rightPad); addChild(m_impl->background); addChild(m_impl->layout); impl->m_externalEvents.mouseEnter.listenStatic([this](const Vector2D& pos) { m_impl->primedForUp = false; m_impl->background ->rebuild() // ->borderColor([] { return g_palette->m_colors.alternateBase.brighten(0.5F); }) ->commence(); }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { m_impl->primedForUp = false; m_impl->background ->rebuild() // ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->commence(); }); m_impl->listeners.clicked = impl->m_externalEvents.mouseButton.listen([this](Input::eMouseButton button, bool down) { if (button != Input::MOUSE_BUTTON_LEFT) return; if (down) { m_impl->primedForUp = true; return; } if (!m_impl->primedForUp) return; openDropdown(); }); } void CComboboxElement::openDropdown() { if (m_impl->dropdown.popup) return; const Vector2D POPUP_SIZE = Vector2D{ 100.F + impl->position.size().x + (2 * DROPDOWN_BUTTON_PAD), std::min( // (DROPDOWN_BUTTON_HEIGHT * m_impl->data.items.size()) + (DROPDOWN_BUTTON_PAD * (m_impl->data.items.size() - 1)) + (2 * DROPDOWN_BUTTON_PAD) + (2 * DROPDOWN_MARGIN_OUTER), // 600.F // ), }; m_impl->dropdown.popup = impl->window->openPopup(SWindowCreationData{ .preferredSize = POPUP_SIZE, .pos = impl->position.pos() - Vector2D{50.F, 0.F} + Vector2D{0.F, impl->position.size().y}, }); m_impl->listeners.popupClosed = m_impl->dropdown.popup->m_events.popupClosed.listen([this, self = m_impl->self] { if (!self) return; m_impl->dropdown = {}; }); m_impl->dropdown.scroll = CScrollAreaBuilder::begin()->scrollY(true)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence(); m_impl->dropdown.layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(DROPDOWN_BUTTON_PAD)->commence(); m_impl->dropdown.layout->setMargin(DROPDOWN_BUTTON_PAD); m_impl->dropdown.background = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.background; }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}) ->rounding(g_palette->m_vars.bigRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(1) ->commence(); m_impl->dropdown.background->setMargin(1); m_impl->dropdown.scroll->setMargin(DROPDOWN_MARGIN_OUTER); m_impl->dropdown.popup->m_rootElement->addChild(m_impl->dropdown.background); m_impl->dropdown.background->addChild(m_impl->dropdown.scroll); m_impl->dropdown.scroll->addChild(m_impl->dropdown.layout); m_impl->dropdown.buttons.clear(); for (size_t i = 0; i < m_impl->data.items.size(); ++i) { m_impl->dropdown.buttons.emplace_back(CButtonBuilder::begin() ->label(std::string{m_impl->data.items.at(i)}) ->noBorder(true) ->noBg(true) ->alignText(HT_FONT_ALIGN_LEFT) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, DROPDOWN_BUTTON_HEIGHT}}) ->onMainClick([i, this, self = m_impl->self](SP e) { if (!self) return; setSelection(i); if (m_impl->data.onChanged) m_impl->data.onChanged(m_impl->self.lock(), i); g_backend->addIdle([this, self = m_impl->self] { if (!self) return; closeDropdown(); }); }) ->commence() // ); m_impl->dropdown.layout->addChild(m_impl->dropdown.buttons.back()); } m_impl->dropdown.popup->open(); impl->grouped = true; } void CComboboxElement::closeDropdown() { if (!m_impl->dropdown.popup) return; m_impl->dropdown.popup->close(); } void CComboboxElement::setSelection(size_t idx) { setCurrent(idx); } size_t CComboboxElement::current() { return m_impl->data.currentItem; } void CComboboxElement::setCurrent(size_t current) { m_impl->data.currentItem = std::min(current, m_impl->data.items.size() - 1); m_impl->label ->rebuild() // ->text(std::string{m_impl->data.items.at(m_impl->data.currentItem)}) ->commence(); } void CComboboxElement::replaceData(const SComboboxData& data) { m_impl->data = data; setCurrent(data.currentItem); if (impl->window) impl->window->scheduleReposition(impl->self); } void CComboboxElement::paint() { ; } SP CComboboxElement::rebuild() { auto p = SP(new CComboboxBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CComboboxElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } Hyprutils::Math::Vector2D CComboboxElement::size() { return impl->position.size(); } std::optional CComboboxElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_impl->data.size.calculate(parent); } std::optional CComboboxElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return m_impl->data.size.calculate(parent); } std::optional CComboboxElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return m_impl->data.size.calculate(parent); } bool CComboboxElement::acceptsMouseInput() { return true; } ePointerShape CComboboxElement::pointerShape() { return HT_POINTER_ARROW; } bool CComboboxElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/combobox/Combobox.hpp000066400000000000000000000062221513450241200242170ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "../../helpers/Memory.hpp" #include "../../helpers/Signal.hpp" #include "../../renderer/Polygon.hpp" #include "../../core/AnimatedVariable.hpp" namespace Hyprtoolkit { class CComboboxClickable; class CButtonElement; class CColumnLayoutElement; class CScrollAreaElement; class IWindow; struct SDropdownHandleData { CDynamicSize size = {CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; colorFn color = [] { return CHyprColor{1.F, 0.F, 0.F}; }; }; class CDropdownHandleElement : public IElement { public: static Hyprutils::Memory::CSharedPointer create(const SDropdownHandleData& data); virtual ~CDropdownHandleElement() = default; private: CDropdownHandleElement(const SDropdownHandleData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual Hyprutils::Math::Vector2D size(); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); SDropdownHandleData m_data; CPolygon m_poly; friend class CCheckboxElement; }; struct SComboboxData { std::vector items = {"Item A", "Item B"}; size_t currentItem = 0; std::function, size_t)> onChanged; CDynamicSize size{CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {}}; }; struct SComboboxImpl { SComboboxData data; WP self; SP background; SP layout; SP label; SP handle; SP leftPad, rightPad, middlePad; struct { SP popup; SP background; SP layout; SP scroll; std::vector> buttons; } dropdown; bool primedForUp = false; struct { CHyprSignalListener clicked; CHyprSignalListener popupClosed; } listeners; }; } hyprwm-hyprtoolkit-71515e8/src/element/combobox/DropdownHandle.cpp000066400000000000000000000032131513450241200253470ustar00rootroot00000000000000#include "Combobox.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CDropdownHandleElement::create(const SDropdownHandleData& data) { auto p = SP(new CDropdownHandleElement(data)); p->impl->self = p; return p; } CDropdownHandleElement::CDropdownHandleElement(const SDropdownHandleData& data) : IElement(), m_data(data), m_poly(CPolygon::dropdown()) { ; } void CDropdownHandleElement::paint() { g_renderer->renderPolygon(IRenderer::SPolygonRenderData{ .box = impl->position, .color = m_data.color(), .poly = m_poly, }); } void CDropdownHandleElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } Hyprutils::Math::Vector2D CDropdownHandleElement::size() { return impl->position.size(); } std::optional CDropdownHandleElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } std::optional CDropdownHandleElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } std::optional CDropdownHandleElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } hyprwm-hyprtoolkit-71515e8/src/element/image/000077500000000000000000000000001513450241200212065ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/image/Builder.cpp000066400000000000000000000025561513450241200233100ustar00rootroot00000000000000#include "Image.hpp" using namespace Hyprtoolkit; SP CImageBuilder::begin() { SP p = SP(new CImageBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CImageBuilder::path(std::string&& s) { m_data->path = std::move(s); return m_self.lock(); } SP CImageBuilder::a(float a) { m_data->a = a; return m_self.lock(); } Hyprutils::Memory::CSharedPointer CImageBuilder::sync(bool x) { m_data->sync = x; return m_self.lock(); } SP CImageBuilder::fitMode(eImageFitMode x) { m_data->fitMode = x; return m_self.lock(); } SP CImageBuilder::rounding(int x) { m_data->rounding = x; return m_self.lock(); } SP CImageBuilder::icon(const SP& x) { m_data->icon = x; return m_self.lock(); } SP CImageBuilder::data(std::vector&& x) { m_data->data = std::move(x); return m_self.lock(); } SP CImageBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CImageBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CImageElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/image/Image.cpp000066400000000000000000000161671513450241200227470ustar00rootroot00000000000000#include "Image.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../core/InternalBackend.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../system/Icons.hpp" #include "../../resource/assetCache/AssetCache.hpp" #include "../../renderer/RendererTexture.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CImageElement::create(const SImageData& data) { auto p = SP(new CImageElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CImageElement::CImageElement(const SImageData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CImageElement::paint() { if (m_impl->failed) return; auto assetToUse = m_impl->cacheEntry; if (!assetToUse || !assetToUse->tex()) assetToUse = m_impl->oldCacheEntry; if (!assetToUse || !assetToUse->tex()) { if (!m_impl->waitingForTex) renderTex(); return; } if (impl->window) m_impl->lastScale = impl->window->scale(); if (m_impl->data.icon && m_impl->preferredSvgSize() != m_impl->size && !m_impl->waitingForTex) { renderTex(); assetToUse = m_impl->oldCacheEntry; } if (!assetToUse || !assetToUse->tex()) return; // ??? g_renderer->renderTexture({ .box = impl->position, .texture = assetToUse->tex(), .a = 1.F, .rounding = 0, }); } void CImageElement::renderTex() { if (m_impl->waitingForTex) return; m_impl->resource.reset(); m_impl->oldCacheEntry = m_impl->cacheEntry; m_impl->cacheEntry.reset(); const auto CACHE_STR = m_impl->getCacheString(); const auto ASSET = Asset::assetCache()->get(CACHE_STR); if (ASSET) { g_logger->log(HT_LOG_DEBUG, "CImageElement: path {} was already cached, reusing entry", m_impl->data.path); m_impl->failed = false; m_impl->cacheEntry = ASSET; if (ASSET->status() == Asset::CACHE_ENTRY_DONE) m_impl->postImageScheduleRecalc(); else { m_impl->listeners.cacheEntryDone = ASSET->m_events.done.listen([this] { m_impl->postImageScheduleRecalc(); m_impl->listeners.cacheEntryDone.reset(); }); } return; } if (!m_impl->data.data.empty()) { const auto SIZE = m_impl->preferredSvgSize(); m_impl->resource = makeAtomicShared(m_impl->data.data, SIZE); m_impl->lastData = m_impl->data.data.data(); } else if (!m_impl->data.icon) { m_impl->resource = makeAtomicShared(m_impl->data.path); m_impl->lastPath = m_impl->data.path; } else { m_impl->lastPath = reinterpretPointerCast(m_impl->data.icon)->m_bestPath; const auto SIZE = m_impl->preferredSvgSize(); m_impl->resource = makeAtomicShared(m_impl->lastPath, SIZE); if (SIZE.x == 0 || SIZE.y == 0) return; } m_impl->cacheEntry = makeShared(CACHE_STR); Asset::assetCache()->cache(m_impl->cacheEntry); m_impl->waitingForTex = true; ASP resourceGeneric(m_impl->resource); g_asyncResourceGatherer->enqueue(resourceGeneric); if (!m_impl->data.sync) { m_impl->resource->m_events.finished.listenStatic([this, self = impl->self] { if (!self) return; g_backend->addIdle([this, self = self]() { if (!self) return; m_impl->postImageLoad(); }); }); } else { g_asyncResourceGatherer->await(resourceGeneric); m_impl->postImageLoad(); } } void SImageImpl::postImageLoad() { if (!resource || !cacheEntry) return; if (resource->m_asset.cairoSurface) { ASP resourceGeneric(resource); size = resource->m_asset.pixelSize; cacheEntry->texDone(g_renderer->uploadTexture({.resource = resourceGeneric, .fitMode = data.fitMode})); } else { failed = true; g_logger->log(HT_LOG_ERROR, "Image: failed loading, hyprgraphics couldn't load asset {}", lastPath); } oldCacheEntry.reset(); resource.reset(); postImageScheduleRecalc(); } void SImageImpl::postImageScheduleRecalc() { waitingForTex = false; if (!failed) { if (cacheEntry && cacheEntry->tex()) size = cacheEntry->tex()->size(); self->impl->damageEntire(); if (self->impl->window) self->impl->window->scheduleReposition(self); } } std::string SImageImpl::getCacheString() { if (!data.data.empty()) // uncacheable return std::format("data-{:x}", rc(data.data.data())); if (!data.icon) return data.path.ends_with(".svg") ? std::format("{}-{}x{}", data.path, preferredSvgSize().x, preferredSvgSize().y) : data.path; else return std::format("icon-{}-{}x{}", reinterpretPointerCast(data.icon)->m_bestPath, preferredSvgSize().x, preferredSvgSize().y); } SP CImageElement::rebuild() { auto p = SP(new CImageBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CImageElement::replaceData(const SImageData& data) { m_impl->data = data; renderTex(); if (impl->window) impl->window->scheduleReposition(impl->self); } void CImageElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } Hyprutils::Math::Vector2D CImageElement::size() { return impl->position.size(); } std::optional CImageElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const float SCALE = impl->window ? impl->window->scale() : 1.F; if (s.x == -1 && s.y == -1) return m_impl->size / SCALE; if (m_impl->size.y == 0) return impl->getPreferredSizeGeneric(m_impl->data.size, parent); const double ASPECT_RATIO = m_impl->size.x / m_impl->size.y; if (s.y == -1) return Vector2D{s.x, s.x * (1 / ASPECT_RATIO)}; return Vector2D{ASPECT_RATIO * s.y, s.y}; } std::optional CImageElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return Vector2D{0, 0}; } std::optional CImageElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return std::nullopt; } bool CImageElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } Vector2D SImageImpl::preferredSvgSize() { auto max = std::max(self->impl->position.size().x, self->impl->position.size().y); return Vector2D{max * lastScale, max * lastScale}.round(); } hyprwm-hyprtoolkit-71515e8/src/element/image/Image.hpp000066400000000000000000000041341513450241200227430ustar00rootroot00000000000000#include #include "../../helpers/Memory.hpp" #include "../../resource/assetCache/AssetCacheEntry.hpp" namespace Hyprtoolkit { struct SImageData { std::string path; float a = 1.F; int rounding = 0; bool sync = false; SP icon; std::vector data; eImageFitMode fitMode = IMAGE_FIT_MODE_STRETCH; CDynamicSize size{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; }; struct SImageImpl { SImageData data; WP self; float lastScale = 1.F; Hyprutils::Memory::CSharedPointer cacheEntry; Hyprutils::Memory::CSharedPointer oldCacheEntry; // while loading a new one Hyprutils::Memory::CAtomicSharedPointer resource; Hyprutils::Math::Vector2D size; bool waitingForTex = false, failed = false; std::string lastPath = ""; void* lastData = nullptr; Hyprutils::Math::Vector2D preferredSvgSize(); void postImageLoad(); void postImageScheduleRecalc(); std::string getCacheString(); struct { Hyprutils::Signal::CHyprSignalListener cacheEntryDone; } listeners; }; } hyprwm-hyprtoolkit-71515e8/src/element/line/000077500000000000000000000000001513450241200210535ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/line/Builder.cpp000066400000000000000000000016261513450241200231520ustar00rootroot00000000000000#include "Line.hpp" using namespace Hyprtoolkit; SP CLineBuilder::begin() { SP p = SP(new CLineBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CLineBuilder::color(colorFn&& f) { m_data->color = std::move(f); return m_self.lock(); } SP CLineBuilder::points(std::vector&& f) { m_data->points = std::move(f); return m_self.lock(); } SP CLineBuilder::thick(int x) { m_data->thick = x; return m_self.lock(); } SP CLineBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CLineBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CLineElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/line/Line.cpp000066400000000000000000000042311513450241200224460ustar00rootroot00000000000000#include "Line.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CLineElement::create(const SLineData& data) { auto p = SP(new CLineElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CLineElement::CLineElement(const SLineData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CLineElement::paint() { g_renderer->renderLine({ .box = impl->position, .points = m_impl->data.points, .color = m_impl->data.color(), .thick = m_impl->data.thick, }); } void CLineElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CLineElement::rebuild() { auto p = SP(new CLineBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CLineElement::replaceData(const SLineData& data) { m_impl->data = data; if (impl->window) impl->window->scheduleReposition(impl->self); } void CLineElement::recheckColor() { ; } Hyprutils::Math::Vector2D CLineElement::size() { return impl->position.size(); } std::optional CLineElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } std::optional CLineElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return Vector2D{0, 0}; } std::optional CLineElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return std::nullopt; } bool CLineElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/line/Line.hpp000066400000000000000000000011041513450241200224470ustar00rootroot00000000000000#pragma once #include #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { struct SLineData { colorFn color = [] { return CHyprColor{1.F, 1.F, 1.F, 1.F}; }; int thick = 2; std::vector points; CDynamicSize size{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; }; struct SLineImpl { SLineData data; WP self; }; } hyprwm-hyprtoolkit-71515e8/src/element/null/000077500000000000000000000000001513450241200210765ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/null/Builder.cpp000066400000000000000000000010551513450241200231710ustar00rootroot00000000000000#include "Null.hpp" using namespace Hyprtoolkit; SP CNullBuilder::begin() { SP p = SP(new CNullBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CNullBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CNullBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CNullElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/null/Null.cpp000066400000000000000000000036661513450241200225270ustar00rootroot00000000000000#include "Null.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CNullElement::create(const SNullData& data) { auto p = SP(new CNullElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CNullElement::CNullElement(const SNullData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CNullElement::paint() { ; } void CNullElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CNullElement::rebuild() { auto p = SP(new CNullBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CNullElement::replaceData(const SNullData& data) { m_impl->data = data; if (impl->window) impl->window->scheduleReposition(impl->self); } Hyprutils::Math::Vector2D CNullElement::size() { return impl->position.size(); } std::optional CNullElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } std::optional CNullElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return Vector2D{0, 0}; } std::optional CNullElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return std::nullopt; } bool CNullElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/null/Null.hpp000066400000000000000000000004771513450241200225310ustar00rootroot00000000000000#include #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { struct SNullData { CDynamicSize size{CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}; }; struct SNullImpl { SNullData data; WP self; }; }hyprwm-hyprtoolkit-71515e8/src/element/rectangle/000077500000000000000000000000001513450241200220705ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/rectangle/Builder.cpp000066400000000000000000000021531513450241200241630ustar00rootroot00000000000000#include "Rectangle.hpp" using namespace Hyprtoolkit; SP CRectangleBuilder::begin() { SP p = SP(new CRectangleBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CRectangleBuilder::color(colorFn&& f) { m_data->color = std::move(f); return m_self.lock(); } SP CRectangleBuilder::borderColor(colorFn&& f) { m_data->borderColor = std::move(f); return m_self.lock(); } SP CRectangleBuilder::rounding(int x) { m_data->rounding = x; return m_self.lock(); } SP CRectangleBuilder::borderThickness(int x) { m_data->borderThickness = x; return m_self.lock(); } SP CRectangleBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CRectangleBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CRectangleElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/rectangle/Rectangle.cpp000066400000000000000000000073431513450241200245070ustar00rootroot00000000000000#include "Rectangle.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CRectangleElement::create(const SRectangleData& data) { auto p = SP(new CRectangleElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CRectangleElement::CRectangleElement(const SRectangleData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; g_animationManager->createAnimation(data.color(), m_impl->color, g_animationManager->m_animationTree.getConfig("fast")); m_impl->color->setUpdateCallback([this](auto) { impl->damageEntire(); }); m_impl->color->setCallbackOnBegin([this](auto) { impl->damageEntire(); }, false); g_animationManager->createAnimation(data.borderColor(), m_impl->borderColor, g_animationManager->m_animationTree.getConfig("fast")); m_impl->borderColor->setUpdateCallback([this](auto) { if (m_impl->data.borderThickness) impl->damageEntire(); }); m_impl->borderColor->setCallbackOnBegin( [this](auto) { if (m_impl->data.borderThickness) impl->damageEntire(); }, false); } void CRectangleElement::paint() { g_renderer->renderRectangle({ .box = impl->position, .color = m_impl->color->value(), .rounding = m_impl->data.rounding, }); if (m_impl->data.borderThickness > 0) { g_renderer->renderBorder({ .box = impl->position, .color = m_impl->borderColor->value(), .rounding = m_impl->data.rounding, .thick = m_impl->data.borderThickness, }); } } void CRectangleElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CRectangleElement::rebuild() { auto p = SP(new CRectangleBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CRectangleElement::replaceData(const SRectangleData& data) { m_impl->data = data; *m_impl->color = data.color(); *m_impl->borderColor = data.borderColor(); if (impl->window) impl->window->scheduleReposition(impl->self); } void CRectangleElement::recheckColor() { *m_impl->color = m_impl->data.color(); *m_impl->borderColor = m_impl->data.borderColor(); } Hyprutils::Math::Vector2D CRectangleElement::size() { return impl->position.size(); } std::optional CRectangleElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } std::optional CRectangleElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return Vector2D{0, 0}; } std::optional CRectangleElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return std::nullopt; } bool CRectangleElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } CBox CRectangleElement::opaqueBox() { if (m_impl->color->value().a != 1.F) return {}; CBox opaque = impl->position; opaque.x = 0; opaque.y = 0; opaque.expand(-(m_impl->data.rounding + m_impl->data.borderThickness)); return opaque; } hyprwm-hyprtoolkit-71515e8/src/element/rectangle/Rectangle.hpp000066400000000000000000000013101513450241200245000ustar00rootroot00000000000000#pragma once #include #include "../../core/AnimatedVariable.hpp" namespace Hyprtoolkit { struct SRectangleData { colorFn color = [] { return CHyprColor{1.F, 1.F, 1.F, 1.F}; }; int rounding = 0; colorFn borderColor = [] { return CHyprColor{1.F, 1.F, 1.F, 1.F}; }; int borderThickness = 0; CDynamicSize size{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; }; struct SRectangleImpl { SRectangleData data; PHLANIMVAR color; PHLANIMVAR borderColor; WP self; }; } hyprwm-hyprtoolkit-71515e8/src/element/rowLayout/000077500000000000000000000000001513450241200221315ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/rowLayout/Builder.cpp000066400000000000000000000013361513450241200242260ustar00rootroot00000000000000#include "RowLayout.hpp" using namespace Hyprtoolkit; SP CRowLayoutBuilder::begin() { SP p = SP(new CRowLayoutBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CRowLayoutBuilder::gap(size_t x) { m_data->gap = x; return m_self.lock(); } SP CRowLayoutBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CRowLayoutBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CRowLayoutElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/rowLayout/RowLayout.cpp000066400000000000000000000166421513450241200246130ustar00rootroot00000000000000#include "RowLayout.hpp" #include #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../core/InternalBackend.hpp" #include "../../window/ToolkitWindow.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CRowLayoutElement::create(const SRowLayoutData& data) { auto p = SP(new CRowLayoutElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CRowLayoutElement::CRowLayoutElement(const SRowLayoutData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CRowLayoutElement::paint() { ; // no-op } void CRowLayoutElement::replaceData(const SRowLayoutData& data) { m_impl->data = data; if (impl->window) impl->window->scheduleReposition(impl->self); } // FIXME: de-dup with columnlayout? void CRowLayoutElement::reposition(const Hyprutils::Math::CBox& sbox, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(sbox); const auto& box = impl->position; const auto C = impl->children; // position children in this layout. float usedX = 0; const float MAX_X = (uint64_t)impl->position.size().x; size_t i = 0; std::vector widths; widths.resize(impl->children.size()); for (i = 0; i < C.size(); ++i) { const auto& child = C.at(i); Vector2D cSize = childSize(child); if (cSize == Vector2D{-1, -1}) cSize = {1.F, box.h}; if (usedX + cSize.x > MAX_X + 2 /* FIXME: WHERE THE FUCK IS THIS LOST??? */) { // we exceeded our available space. if (!child->minimumSize(box.size())) { // doesn't fit: disable child->impl->setFailedPositioning(true); continue; } cSize = *child->minimumSize(box.size()); if (usedX + cSize.x > MAX_X + 1) { // doesn't fit: try to shrink any previous element if it allows to do so // FIXME: if we resize and fail to fit, it will mess up the layout a bit float needs = (usedX + cSize.x) - (MAX_X + 1); for (int j = i - 1; j >= 0; --j) { const auto& prevChild = C.at(j); const auto MIN = prevChild->minimumSize(impl->position.size()); if (!MIN.has_value()) { // we can shrink this child to zero if (needs > widths.at(j)) { widths.at(j) -= needs - widths.at(j); needs -= needs - widths.at(j); continue; } widths.at(j) -= needs; needs = 0; break; // done } else if (MIN->x < widths.at(j)) { // we can shrink it a bit if (needs > (widths.at(j) - MIN->x)) { widths.at(j) -= needs - (widths.at(j) - MIN->x); needs -= needs - (widths.at(j) - MIN->x); continue; } widths.at(j) -= needs; needs = 0; break; // done } } if (needs > 0) { // doesn't fit: disable and expand the last if possible child->impl->setFailedPositioning(true); if (i != 0) { // try to expand last child const auto& lastChild = C.at(i - 1); if (lastChild->maximumSize(box.size()) && widths.at(i - 1) + MAX_X - usedX > lastChild->maximumSize(box.size())->x) continue; // too bad, we'll have a gap widths.at(i - 1) += MAX_X - usedX; } continue; } else { child->impl->setFailedPositioning(false); widths.at(i) = cSize.x; // recalc usedX cuz we changed stuff usedX = 0; for (const auto& w : widths) { usedX += w + m_impl->data.gap; } continue; } } // squeeze the last element in widths.at(i) = MAX_X - usedX; continue; } // can fit: use preferred widths.at(i) = cSize.x; child->impl->setFailedPositioning(false); usedX += cSize.x + m_impl->data.gap; } if (!C.empty()) usedX -= m_impl->data.gap; // check if any element grows and grow if applicable if (usedX < MAX_X) { for (i = 0; i < C.size(); ++i) { const auto& child = C.at(i); if (!child->impl->growH) continue; widths.at(i) += MAX_X - usedX; break; } } // widths done: lay out elements size_t currentX = 0; for (i = 0; i < C.size(); ++i) { const auto& child = C.at(i); if (child->impl->failedPositioning) continue; Vector2D cSize = childSize(child); cSize.y = std::clamp(cSize.y, 0.0, impl->position.h); CBox childBox = CBox{box.x + currentX, box.y + ((box.h - cSize.y) / 2), (double)widths.at(i), cSize.y}; g_positioner->position(child, childBox, Vector2D{childBox.w + (MAX_X - usedX) /* this can potentially cause problems... ↓ */, box.h}); // This essentially tells each item it can grow, so if we have two // texts next to each other, they might fight for space. FIXME: currentX += childBox.w + m_impl->data.gap; } } Hyprutils::Math::Vector2D CRowLayoutElement::childSize(Hyprutils::Memory::CSharedPointer child) { if (child->preferredSize(impl->position.size())) return *child->preferredSize(impl->position.size()); else if (child->minimumSize(impl->position.size())) return *child->minimumSize(impl->position.size()); return {-1, -1}; } Hyprutils::Math::Vector2D CRowLayoutElement::size() { return impl->position.size(); } std::optional CRowLayoutElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { auto calc = m_impl->data.size.calculate(parent); if (calc.x != -1 && calc.y != -1) return calc; Vector2D max; for (const auto& child : impl->children) { max.x += childSize(child).x + m_impl->data.gap; max.y = std::max(max.y, childSize(child).y); } if (!impl->children.empty()) max.x -= m_impl->data.gap; max.x += impl->margin * 2; max.y += impl->margin * 2; max.x = std::ceil(max.x); max.y = std::ceil(max.y); if (calc.x == -1) calc.x = max.x; if (calc.y == -1) calc.y = max.y; return calc; } std::optional CRowLayoutElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { Vector2D min; for (const auto& child : impl->children) { min.y = std::max(min.y, childSize(child).y); } return min; } bool CRowLayoutElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/rowLayout/RowLayout.hpp000066400000000000000000000006061513450241200246110ustar00rootroot00000000000000#include #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { struct SRowLayoutData { CDynamicSize size = CDynamicSize(CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}); size_t gap = 0; }; struct SRowLayoutImpl { SRowLayoutData data; WP self; }; } hyprwm-hyprtoolkit-71515e8/src/element/scrollArea/000077500000000000000000000000001513450241200222135ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/scrollArea/Builder.cpp000066400000000000000000000017521513450241200243120ustar00rootroot00000000000000#include "ScrollArea.hpp" using namespace Hyprtoolkit; SP CScrollAreaBuilder::begin() { SP p = SP(new CScrollAreaBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CScrollAreaBuilder::scrollX(bool x) { m_data->scrollX = x; return m_self.lock(); } SP CScrollAreaBuilder::scrollY(bool x) { m_data->scrollY = x; return m_self.lock(); } SP CScrollAreaBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CScrollAreaBuilder::blockUserScroll(bool x) { m_data->blockUserScroll = x; return m_self.lock(); } SP CScrollAreaBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CScrollAreaElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/scrollArea/ScrollArea.cpp000066400000000000000000000072501513450241200247520ustar00rootroot00000000000000#include "ScrollArea.hpp" #include #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../core/InternalBackend.hpp" #include "../../window/ToolkitWindow.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; SP CScrollAreaElement::create(const SScrollAreaData& data) { auto p = SP(new CScrollAreaElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CScrollAreaElement::CScrollAreaElement(const SScrollAreaData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; impl->clipChildren = true; m_impl->listeners.axis = impl->m_externalEvents.mouseAxis.listen([this](Input::eAxisAxis axis, float delta) { if (m_impl->data.blockUserScroll) return; const bool SCROLLING_X = axis == Input::AXIS_AXIS_HORIZONTAL; if (SCROLLING_X && !m_impl->data.scrollX) return; if (!SCROLLING_X && !m_impl->data.scrollY) return; if (SCROLLING_X) m_impl->data.currentScroll.x += delta; else m_impl->data.currentScroll.y += delta; m_impl->clampMaxScroll(); if (impl->window) impl->window->scheduleReposition(impl->self); }); } void CScrollAreaElement::paint() { ; // no-op } void CScrollAreaElement::replaceData(const SScrollAreaData& data) { m_impl->data = data; if (impl->window) impl->window->scheduleReposition(impl->self); } void CScrollAreaElement::reposition(const Hyprutils::Math::CBox& sbox, const Vector2D& maxSize) { IElement::reposition(sbox); m_impl->clampMaxScroll(); g_positioner->positionChildren(impl->self.lock(), { .offset = -m_impl->data.currentScroll.round(), .growX = m_impl->data.scrollX, .growY = m_impl->data.scrollY, }); } Vector2D CScrollAreaElement::size() { return impl->position.size(); } Vector2D CScrollAreaElement::getCurrentScroll() { return m_impl->data.currentScroll; } void CScrollAreaElement::setScroll(const Hyprutils::Math::Vector2D& x) { m_impl->data.currentScroll = x; m_impl->clampMaxScroll(); if (impl->window) impl->window->scheduleReposition(impl->self); } std::optional CScrollAreaElement::preferredSize(const Vector2D& parent) { return impl->getPreferredSizeGeneric(m_impl->data.size, parent); } std::optional CScrollAreaElement::minimumSize(const Vector2D& parent) { return Vector2D{0, 0}; } bool CScrollAreaElement::acceptsMouseInput() { return true; } ePointerShape CScrollAreaElement::pointerShape() { return HT_POINTER_ARROW; } bool CScrollAreaElement::alwaysGetMouseInput() { return true; } bool CScrollAreaElement::positioningDependsOnChild() { return false; } void SScrollAreaImpl::clampMaxScroll() { // recheck limits if (self->impl->children.empty() || !self->impl->children.at(0)->impl->positionerData) return; Vector2D scrollMax = (self->impl->children.at(0) ->preferredSize({ data.scrollX ? 99999999999 : self->impl->position.w, data.scrollY ? 99999999999 : self->impl->position.h, }) .value_or(Vector2D{99999999, 99999999}) - self->impl->position.size()) .clamp({0, 0}); data.currentScroll = data.currentScroll.clamp({}, scrollMax); } hyprwm-hyprtoolkit-71515e8/src/element/scrollArea/ScrollArea.hpp000066400000000000000000000014111513450241200247500ustar00rootroot00000000000000#include #include "../../helpers/Memory.hpp" #include "../../helpers/Signal.hpp" namespace Hyprtoolkit { struct SScrollAreaData { CDynamicSize size = CDynamicSize(CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}); bool scrollX = false; bool scrollY = false; bool blockUserScroll = false; Hyprutils::Math::Vector2D currentScroll; }; struct SScrollAreaImpl { SScrollAreaData data; WP self; void clampMaxScroll(); struct { CHyprSignalListener axis; } listeners; }; } hyprwm-hyprtoolkit-71515e8/src/element/slider/000077500000000000000000000000001513450241200214065ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/slider/Builder.cpp000066400000000000000000000022511513450241200235000ustar00rootroot00000000000000#include "Slider.hpp" using namespace Hyprtoolkit; SP CSliderBuilder::begin() { SP p = SP(new CSliderBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CSliderBuilder::min(float x) { m_data->min = x; return m_self.lock(); } SP CSliderBuilder::max(float x) { m_data->max = x; return m_self.lock(); } SP CSliderBuilder::val(float x) { m_data->current = x; return m_self.lock(); } SP CSliderBuilder::snapInt(bool x) { m_data->snapInt = x; return m_self.lock(); } SP CSliderBuilder::onChanged(std::function, float)>&& x) { m_data->onChanged = std::move(x); return m_self.lock(); } SP CSliderBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CSliderBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CSliderElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/slider/Slider.cpp000066400000000000000000000171471513450241200233460ustar00rootroot00000000000000#include "Slider.hpp" #include #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CSliderElement::create(const SSliderData& data) { auto p = SP(new CSliderElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CSliderElement::CSliderElement(const SSliderData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; m_impl->layout = CRowLayoutBuilder::begin()->gap(3)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence(); m_impl->layout->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->layout->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_impl->textContainer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {m_impl->maxLabelSize(), 1.F}})->commence(); m_impl->valueText = CTextBuilder::begin() // ->text(m_impl->valueAsText()) ->color([] { return g_palette->m_colors.text; }) ->callback([this] { if (impl->window) impl->window->scheduleReposition(impl->self); }) ->commence(); m_impl->valueText->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->valueText->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_impl->textContainer->addChild(m_impl->valueText); m_impl->background = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.base; }) ->rounding(g_palette->m_vars.smallRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(1) ->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_impl->background->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->background->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_impl->foreground = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.accent; }) ->rounding(g_palette->m_vars.smallRounding) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {(m_impl->data.current / m_impl->data.max), 1.F}}) ->commence(); m_impl->background->addChild(m_impl->foreground); m_impl->layout->addChild(m_impl->background); m_impl->layout->addChild(m_impl->textContainer); addChild(m_impl->layout); impl->m_externalEvents.mouseEnter.listenStatic([this](const Vector2D& pos) { m_impl->dragging = false; m_impl->lastPosLocal = pos; m_impl->background->rebuild()->borderColor([] { return g_palette->m_colors.alternateBase.brighten(0.5F); })->commence(); }); impl->m_externalEvents.mouseMove.listenStatic([this](const Vector2D& pos) { m_impl->lastPosLocal = pos; if (m_impl->dragging) m_impl->updateValue(); }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { m_impl->dragging = false; m_impl->background->rebuild()->borderColor([] { return g_palette->m_colors.alternateBase; })->commence(); }); impl->m_externalEvents.mouseButton.listenStatic([this](const Input::eMouseButton button, bool down) { if (button != Input::MOUSE_BUTTON_LEFT) return; m_impl->dragging = down; if (!m_impl->dragging) return; m_impl->updateValue(); }); impl->grouped = true; } void SSliderImpl::valueChanged(float perc) { data.current = data.max * perc; foreground->rebuild()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {(data.current / data.max), 1.F}})->commence(); textContainer->rebuild()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {maxLabelSize(), 1.F}})->commence(); valueText->rebuild()->text(valueAsText())->commence(); } void SSliderImpl::updateValue() { CBox box = background->impl->position; // expand grab area for the user. box.h += 10; box.y -= 5; const float CURRENT_VALUE = std::clamp(sc(((lastPosLocal + background->impl->position.pos()).x - background->impl->position.pos().x) / background->impl->position.size().x), 0.F, 1.F); valueChanged(CURRENT_VALUE); if (data.onChanged) data.onChanged(self.lock(), CURRENT_VALUE); } void CSliderElement::paint() { ; } bool CSliderElement::sliding() { return m_impl->dragging; } void CSliderElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CSliderElement::rebuild() { auto p = SP(new CSliderBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CSliderElement::replaceData(const SSliderData& data) { const bool VALUE_CHANGED = data.current != m_impl->data.current; m_impl->data = data; if (VALUE_CHANGED) { m_impl->valueChanged(data.current); if (data.onChanged) data.onChanged(m_impl->self.lock(), m_impl->data.current); } if (impl->window) impl->window->scheduleReposition(impl->self); } float SSliderImpl::maxLabelSize() { // TODO: maybe actually calculate it or something? size_t maxChars = std::floor(std::log10(data.max)); if (!data.snapInt) maxChars += 2; return maxChars * 10.F; } std::string SSliderImpl::valueAsText() { if (data.snapInt) return std::format("{}", sc(std::round(data.current))); return std::format("{:.1f}", data.current); } Hyprutils::Math::Vector2D CSliderElement::size() { return impl->position.size(); } std::optional CSliderElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->layout->preferredSize(parent).value() + Vector2D{1, 1}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } std::optional CSliderElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->layout->preferredSize(parent).value() + Vector2D{1, 1}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } std::optional CSliderElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->layout->preferredSize(parent).value() + Vector2D{1, 1}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } bool CSliderElement::acceptsMouseInput() { return true; } ePointerShape CSliderElement::pointerShape() { return HT_POINTER_POINTER; } bool CSliderElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/slider/Slider.hpp000066400000000000000000000027401513450241200233440ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { struct SSliderData { float min = 0, max = 100, current = 50; bool snapInt = true; std::function, float)> onChanged; CDynamicSize size{CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {}}; }; struct SSliderImpl { SSliderData data; WP self; void updateValue(); std::string valueAsText(); void valueChanged(float perc); float maxLabelSize(); SP layout; SP background; SP foreground; SP valueText; SP textContainer; bool dragging = false; Hyprutils::Math::Vector2D lastPosLocal; bool labelChanged = true; }; } hyprwm-hyprtoolkit-71515e8/src/element/spinbox/000077500000000000000000000000001513450241200216065ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/spinbox/Builder.cpp000066400000000000000000000024051513450241200237010ustar00rootroot00000000000000#include "Spinbox.hpp" using namespace Hyprtoolkit; SP CSpinboxBuilder::begin() { SP p = SP(new CSpinboxBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CSpinboxBuilder::label(std::string&& x) { m_data->label = std::move(x); return m_self.lock(); } SP CSpinboxBuilder::items(std::vector&& x) { m_data->items = std::move(x); return m_self.lock(); } SP CSpinboxBuilder::currentItem(size_t x) { m_data->currentItem = x; return m_self.lock(); } SP CSpinboxBuilder::fill(bool x) { m_data->fill = x; return m_self.lock(); } SP CSpinboxBuilder::onChanged(std::function, size_t)>&& x) { m_data->onChanged = std::move(x); return m_self.lock(); } SP CSpinboxBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CSpinboxBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CSpinboxElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/spinbox/Spinbox.cpp000066400000000000000000000102711513450241200237350ustar00rootroot00000000000000#include "Spinbox.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" #include "../../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CSpinboxElement::create(const SSpinboxData& data) { auto p = SP(new CSpinboxElement(data)); p->impl->self = p; p->m_impl->self = p; p->init(); return p; } CSpinboxElement::CSpinboxElement(const SSpinboxData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CSpinboxElement::init() { RASSERT(!m_impl->data.items.empty(), "Spinbox can't be empty"); m_impl->layout = CRowLayoutBuilder::begin()->gap(3)->size({m_impl->data.fill ? CDynamicSize::HT_SIZE_PERCENT : CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); m_impl->label = CTextBuilder::begin() ->text(std::string{m_impl->data.label}) ->color([] { return g_palette->m_colors.text; }) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->commence(); m_impl->spinner = CSpinboxSpinner::create(m_impl->self.lock()); m_impl->spacer = CNullBuilder::begin()->commence(); m_impl->spacer->setGrow(true, true); m_impl->layout->addChild(m_impl->label); m_impl->layout->addChild(m_impl->spacer); m_impl->layout->addChild(m_impl->spinner); addChild(m_impl->layout); } void CSpinboxElement::paint() { ; } void CSpinboxElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } void CSpinboxElement::replaceData(const SSpinboxData& data) { m_impl->data = data; m_impl->label->rebuild()->text(std::string{data.label})->commence(); if (impl->window) impl->window->scheduleReposition(impl->self); } SP CSpinboxElement::rebuild() { auto p = SP(new CSpinboxBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } size_t CSpinboxElement::current() { return m_impl->data.currentItem; } void CSpinboxElement::setCurrent(size_t current) { m_impl->data.currentItem = std::min(current, m_impl->data.items.size() - 1); m_impl->spinner->m_label ->rebuild() // ->text(std::string{m_impl->data.items.at(m_impl->data.currentItem)}) ->commence(); } Hyprutils::Math::Vector2D CSpinboxElement::size() { return impl->position.size(); } std::optional CSpinboxElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->layout->preferredSize(parent).value() + Vector2D{1, 1}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } std::optional CSpinboxElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->layout->preferredSize(parent).value() + Vector2D{1, 1}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } std::optional CSpinboxElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; const auto CALC = m_impl->layout->preferredSize(parent).value() + Vector2D{1, 1}; if (s.x == -1) s.x = CALC.x; if (s.y == -1) s.y = CALC.y; return s; } bool CSpinboxElement::acceptsMouseInput() { return false; } ePointerShape CSpinboxElement::pointerShape() { return HT_POINTER_POINTER; } bool CSpinboxElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/spinbox/Spinbox.hpp000066400000000000000000000120731513450241200237440ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "../../helpers/Memory.hpp" #include "../../renderer/Polygon.hpp" #include "../../core/AnimatedVariable.hpp" namespace Hyprtoolkit { class CSpinboxSpinner; struct SSpinboxAngleData { CDynamicSize size = {CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; colorFn color = [] { return CHyprColor{1.F, 0.F, 0.F}; }; colorFn colorActive = [] { return CHyprColor{1.F, 1.F, 0.F}; }; bool right = false; WP spinner; }; class CSpinboxAngleElement : public IElement { public: static Hyprutils::Memory::CSharedPointer create(const SSpinboxAngleData& data); virtual ~CSpinboxAngleElement() = default; private: CSpinboxAngleElement(const SSpinboxAngleData& data); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual Hyprutils::Math::Vector2D size(); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); SSpinboxAngleData m_data; CPolygon m_poly; bool m_primedForUp = false; PHLANIMVAR m_color; friend class CCheckboxElement; }; struct SSpinboxData { std::string label = "Choose one"; std::vector items = {"Item A", "Item B"}; size_t currentItem = 0; std::function, size_t)> onChanged; CDynamicSize size{CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {}}; bool fill = false; }; class CSpinboxSpinner : public IElement { public: static Hyprutils::Memory::CSharedPointer create(SP element); virtual ~CSpinboxSpinner() = default; void updateLabel(const std::string& str); private: CSpinboxSpinner(SP element); virtual void paint(); virtual void reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); virtual Hyprutils::Math::Vector2D size(); virtual std::optional preferredSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional minimumSize(const Hyprutils::Math::Vector2D& parent); virtual std::optional maximumSize(const Hyprutils::Math::Vector2D& parent); virtual bool acceptsMouseInput(); virtual ePointerShape pointerShape(); virtual bool alwaysGetMouseInput(); void moveSelection(bool forward); void init(); SP m_parent; SP m_background; SP m_layout; SP m_label; SP m_left; SP m_right; SP m_leftPad, m_rightPad; WP m_self; friend class CSpinboxElement; friend class CSpinboxAngleElement; }; struct SSpinboxImpl { SSpinboxData data; WP self; SP layout; SP label; SP spacer; SP spinner; }; } hyprwm-hyprtoolkit-71515e8/src/element/spinbox/SpinboxAngle.cpp000066400000000000000000000053351513450241200247110ustar00rootroot00000000000000#include "Spinbox.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CSpinboxAngleElement::create(const SSpinboxAngleData& data) { auto p = SP(new CSpinboxAngleElement(data)); p->impl->self = p; return p; } CSpinboxAngleElement::CSpinboxAngleElement(const SSpinboxAngleData& data) : IElement(), m_data(data), m_poly(data.right ? CPolygon::rangle() : CPolygon::langle()) { g_animationManager->createAnimation(data.color(), m_color, g_animationManager->m_animationTree.getConfig("fast")); m_color->setUpdateCallback([this](auto) { impl->damageEntire(); }); m_color->setCallbackOnBegin([this](auto) { impl->damageEntire(); }, false); impl->m_externalEvents.mouseEnter.listenStatic([this](const Vector2D& pos) { m_primedForUp = false; *m_color = m_data.colorActive(); }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { m_primedForUp = false; *m_color = m_data.color(); }); impl->m_externalEvents.mouseButton.listenStatic([this](const Input::eMouseButton button, bool down) { if (button != Input::MOUSE_BUTTON_LEFT) return; if (down) { m_primedForUp = true; return; } if (!m_primedForUp) return; m_data.spinner->moveSelection(m_data.right); }); } void CSpinboxAngleElement::paint() { g_renderer->renderPolygon(IRenderer::SPolygonRenderData{ .box = impl->position, .color = m_color->value(), .poly = m_poly, }); } void CSpinboxAngleElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } Hyprutils::Math::Vector2D CSpinboxAngleElement::size() { return impl->position.size(); } std::optional CSpinboxAngleElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } std::optional CSpinboxAngleElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } std::optional CSpinboxAngleElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return m_data.size.calculate(parent); } bool CSpinboxAngleElement::acceptsMouseInput() { return true; } ePointerShape CSpinboxAngleElement::pointerShape() { return HT_POINTER_POINTER; } hyprwm-hyprtoolkit-71515e8/src/element/spinbox/SpinboxSpinner.cpp000066400000000000000000000122541513450241200252770ustar00rootroot00000000000000#include "Spinbox.hpp" #include #include "../../core/InternalBackend.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/AnimationManager.hpp" #include "../Element.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CSpinboxSpinner::create(SP data) { auto p = SP(new CSpinboxSpinner(data)); p->impl->self = p; p->m_self = p; p->init(); return p; } constexpr float ANGLE_SIZE = 12.F; constexpr float INNER_MARG = 2.F; CSpinboxSpinner::CSpinboxSpinner(SP data) : IElement(), m_parent(data) { ; } void CSpinboxSpinner::init() { m_layout = CRowLayoutBuilder::begin()->gap(6)->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); m_layout->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_layout->setPositionMode(HT_POSITION_ABSOLUTE); m_layout->setMargin(INNER_MARG); m_label = CTextBuilder::begin() ->text(std::string{m_parent->m_impl->data.items.at(m_parent->m_impl->data.currentItem)}) ->color([] { return g_palette->m_colors.text; }) ->callback([this] { impl->window->scheduleReposition(impl->self); }) ->commence(); m_background = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.base; }) ->rounding(g_palette->m_vars.smallRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(1) ->size(CDynamicSize{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_background->setPositionFlag(HT_POSITION_FLAG_CENTER, true); m_background->setPositionMode(HT_POSITION_ABSOLUTE); m_left = CSpinboxAngleElement::create(SSpinboxAngleData{ .size = {CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {ANGLE_SIZE, ANGLE_SIZE}}, .color = [] { return g_palette->m_colors.text; }, .colorActive = [] { return g_palette->m_colors.text.mix(g_palette->m_colors.accent, 0.75F); }, .spinner = m_self, }); m_right = CSpinboxAngleElement::create(SSpinboxAngleData{ .size = {CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {ANGLE_SIZE, ANGLE_SIZE}}, .color = [] { return g_palette->m_colors.text; }, .colorActive = [] { return g_palette->m_colors.text.mix(g_palette->m_colors.accent, 0.75F); }, .right = true, .spinner = m_self, }); m_leftPad = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {0.F, 1.F}})->commence(); m_rightPad = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {0.F, 1.F}})->commence(); m_layout->addChild(m_leftPad); m_layout->addChild(m_left); m_layout->addChild(m_label); m_layout->addChild(m_right); m_layout->addChild(m_rightPad); addChild(m_background); addChild(m_layout); impl->m_externalEvents.mouseEnter.listenStatic([this](const Vector2D& pos) { m_background ->rebuild() // ->borderColor([] { return g_palette->m_colors.alternateBase.brighten(0.5F); }) ->commence(); }); impl->m_externalEvents.mouseLeave.listenStatic([this]() { m_background ->rebuild() // ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->commence(); }); } void CSpinboxSpinner::moveSelection(bool forward) { if (!forward) { // switch left if (m_parent->m_impl->data.currentItem) m_parent->setCurrent(m_parent->m_impl->data.currentItem - 1); else m_parent->setCurrent(m_parent->m_impl->data.items.size() - 1); } else { // switch right if (m_parent->m_impl->data.currentItem) m_parent->setCurrent(m_parent->m_impl->data.currentItem - 1); else m_parent->setCurrent(m_parent->m_impl->data.items.size() - 1); } } void CSpinboxSpinner::paint() { ; } void CSpinboxSpinner::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } Hyprutils::Math::Vector2D CSpinboxSpinner::size() { return impl->position.size(); } std::optional CSpinboxSpinner::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_layout->preferredSize(parent); } std::optional CSpinboxSpinner::minimumSize(const Hyprutils::Math::Vector2D& parent) { return m_layout->preferredSize(parent); } std::optional CSpinboxSpinner::maximumSize(const Hyprutils::Math::Vector2D& parent) { return m_layout->preferredSize(parent); } bool CSpinboxSpinner::acceptsMouseInput() { return true; } ePointerShape CSpinboxSpinner::pointerShape() { return HT_POINTER_ARROW; } bool CSpinboxSpinner::alwaysGetMouseInput() { return true; } hyprwm-hyprtoolkit-71515e8/src/element/text/000077500000000000000000000000001513450241200211105ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/text/Builder.cpp000066400000000000000000000035211513450241200232030ustar00rootroot00000000000000#include "Text.hpp" using namespace Hyprtoolkit; SP CTextBuilder::begin() { SP p = SP(new CTextBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CTextBuilder::color(colorFn&& f) { m_data->color = std::move(f); return m_self.lock(); } SP CTextBuilder::a(float a) { m_data->a = a; return m_self.lock(); } SP CTextBuilder::fontSize(CFontSize&& x) { //NOLINTNEXTLINE m_data->fontSize = std::move(x); return m_self.lock(); } SP CTextBuilder::text(std::string&& x) { m_data->text = std::move(x); return m_self.lock(); } SP CTextBuilder::align(eFontAlignment x) { m_data->align = x; return m_self.lock(); } SP CTextBuilder::fontFamily(std::string&& x) { m_data->fontFamily = std::move(x); return m_self.lock(); } SP CTextBuilder::clampSize(Hyprutils::Math::Vector2D&& x) { m_data->clampSize = std::move(x); return m_self.lock(); } SP CTextBuilder::noEllipsize(bool x) { m_data->noEllipsize = std::move(x); return m_self.lock(); } SP CTextBuilder::callback(std::function&& x) { m_data->callback = std::move(x); return m_self.lock(); } SP CTextBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CTextBuilder::async(bool x) { m_data->async = x; return m_self.lock(); } SP CTextBuilder::interactable(bool x) { m_data->interactable = x; return m_self.lock(); } SP CTextBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CTextElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/text/Text.cpp000066400000000000000000000415071513450241200225470ustar00rootroot00000000000000#include "Text.hpp" #include #include #include #include #include "../../window/ToolkitWindow.hpp" #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../core/InternalBackend.hpp" #include "../../core/Logger.hpp" #include "../../helpers/Memory.hpp" #include "../Element.hpp" #include "../../helpers/UTF8.hpp" #include "../../system/DesktopMethods.hpp" #include "Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprgraphics; SP CTextElement::create(const STextData& data) { auto p = SP(new CTextElement(data)); p->impl->self = p; p->m_impl->self = p; return p; } CTextElement::CTextElement(const STextData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; m_impl->parseText(); m_impl->lastFontSizeUnscaled = m_impl->data.fontSize.ptSize(); m_impl->preferred = m_impl->getTextSizePreferred(); impl->m_externalEvents.mouseMove.listenStatic([this](const Vector2D& pos) { m_impl->lastCursorPos = pos; m_impl->onMouseMove(); }); impl->m_externalEvents.mouseButton.listenStatic([this](const Input::eMouseButton button, bool down) { if (!down) return; m_impl->onMouseDown(); }); } CTextElement::~CTextElement() = default; void CTextElement::replaceData(const STextData& data) { const bool TEXT_DIFFERENT = data.text != m_impl->data.text; m_impl->data = data; if (m_impl->lastFontSizeUnscaled != m_impl->data.fontSize.ptSize() || TEXT_DIFFERENT) { m_impl->parseText(); m_impl->lastFontSizeUnscaled = m_impl->data.fontSize.ptSize(); m_impl->preferred = m_impl->getTextSizePreferred(); m_impl->scheduleTexRefresh(); } if (impl->window) impl->window->scheduleReposition(impl->self); } SP CTextElement::rebuild() { auto p = SP(new CTextBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CTextElement::paint() { SP textureToUse = m_impl->tex; if (!m_impl->tex) textureToUse = m_impl->oldTex; if (!textureToUse) { if (!m_impl->waitingForTex && !m_impl->resource) m_impl->renderTex(); return; } if ((impl->window && impl->window->scale() != m_impl->lastScale) || m_impl->needsTexRefresh) { m_impl->lastScale = impl->window ? impl->window->scale() : 1.F; m_impl->preferred = m_impl->getTextSizePreferred(); if (!m_impl->resource) m_impl->renderTex(); textureToUse = m_impl->oldTex; } if (!textureToUse) return; // ??? if (m_impl->newTex) { m_impl->newTex = false; impl->damageEntire(); } CBox renderBox = impl->position; Vector2D texSizeLogical = m_impl->size / impl->window->scale(); if (impl->positionFlags & HT_POSITION_FLAG_HCENTER) renderBox.translate({(renderBox.size() - texSizeLogical).x / 2, 0.F}); if (impl->positionFlags & HT_POSITION_FLAG_VCENTER) renderBox.translate({0.F, (renderBox.size() - texSizeLogical).y / 2}); renderBox.w = texSizeLogical.x; renderBox.h = texSizeLogical.y; g_renderer->renderTexture({ .box = renderBox, .texture = textureToUse, .a = m_impl->data.a, .rounding = 0, }); } void CTextElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); const auto DESIRED = m_impl->preferred; if (DESIRED.x > 0 && DESIRED.y > 0 && !m_impl->data.noEllipsize) { const auto PREV = m_impl->lastMaxSize; m_impl->lastMaxSize = {-1, -1}; const auto SIZE = box.size(); if (maxSize.x > 0) m_impl->lastMaxSize.x = maxSize.x; if (maxSize.y > 0) m_impl->lastMaxSize.y = maxSize.y; if (SIZE.x + 1 < DESIRED.x) m_impl->lastMaxSize.x = SIZE.x; if (SIZE.y + 1 < DESIRED.y) m_impl->lastMaxSize.y = SIZE.y; if (PREV != m_impl->lastMaxSize) { m_impl->needsTexRefresh = true; const auto LAST_PREF = m_impl->preferred; m_impl->lastScale = impl->window ? impl->window->scale() : 1.F; m_impl->preferred = m_impl->getTextSizePreferred(); if (impl->window && (std::abs(LAST_PREF.x - m_impl->preferred.x) > 2 || std::abs(LAST_PREF.y - m_impl->preferred.y) > 2)) impl->window->scheduleReposition(impl->self.lock()); } } g_positioner->positionChildren(impl->self.lock()); } void CTextElement::recheckColor() { m_impl->needsTexRefresh = true; } Hyprutils::Math::Vector2D CTextElement::size() { return m_impl->unscale(m_impl->size); } std::optional CTextElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { return std::nullopt; } std::optional CTextElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_impl->preferred; } std::optional CTextElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { return Vector2D{0, 0}; } bool CTextElement::acceptsMouseInput() { return m_impl->data.interactable.value_or(!m_impl->parsedLinks.empty()) || IElement::acceptsMouseInput(); } std::function CTextElement::pointerShapeFn() { return [this] { return m_impl->hoveredTextLink ? HT_POINTER_POINTER : HT_POINTER_ARROW; }; } bool CTextElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } std::tuple, cairo_t*, PangoLayout*, Vector2D> STextImpl::prepPangoLayout() { auto CAIROSURFACE = makeUnique(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1 /* dummy value */)); auto CAIRO = cairo_create(CAIROSURFACE->cairo()); PangoLayout* layout = pango_cairo_create_layout(CAIRO); PangoFontDescription* fontDesc = pango_font_description_from_string(data.fontFamily.c_str()); pango_font_description_set_size(fontDesc, std::round(lastFontSizeUnscaled * lastScale) * PANGO_SCALE); pango_layout_set_font_description(layout, fontDesc); pango_font_description_free(fontDesc); if (data.align == HT_FONT_ALIGN_LEFT) pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); else if (data.align == HT_FONT_ALIGN_CENTER) pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); else pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); PangoAttrList* attrList = nullptr; GError* gError = nullptr; char* buf = nullptr; if (pango_parse_markup(parsedText.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) pango_layout_set_text(layout, buf, -1); else { g_error_free(gError); pango_layout_set_text(layout, parsedText.c_str(), -1); } if (!attrList) attrList = pango_attr_list_new(); if (buf) free(buf); pango_attr_list_insert(attrList, pango_attr_scale_new(1)); pango_layout_set_attributes(layout, attrList); pango_attr_list_unref(attrList); int layoutWidth, layoutHeight; pango_layout_get_size(layout, &layoutWidth, &layoutHeight); PangoRectangle ink, logical; pango_layout_get_pixel_extents(layout, &ink, &logical); std::optional maxSize = data.clampSize.value_or(lastMaxSize).round(); if (maxSize == Vector2D{0, 0}) maxSize = std::nullopt; if (maxSize.has_value()) (*maxSize) *= lastScale; if (maxSize.has_value()) { const auto CLAMP_SIZE = maxSize.value(); if (!data.noEllipsize && maxSize.has_value() && maxSize->y >= 0) pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); if (CLAMP_SIZE.x >= 0) pango_layout_set_width(layout, std::min(logical.width * PANGO_SCALE, sc(CLAMP_SIZE.x * PANGO_SCALE))); if (CLAMP_SIZE.y >= 0) pango_layout_set_height(layout, std::min(logical.height * PANGO_SCALE, sc(CLAMP_SIZE.y * PANGO_SCALE))); if (CLAMP_SIZE.x >= 0) pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); pango_layout_get_pixel_extents(layout, &ink, &logical); } pango_layout_get_pixel_extents(layout, &ink, &logical); return std::make_tuple<>(std::move(CAIROSURFACE), CAIRO, layout, Vector2D{logical.width, logical.height}); } Hyprutils::Math::Vector2D STextImpl::getTextSizePreferred() { auto [CAIROSURFACE, CAIRO, LAYOUT, LAYOUTSIZE] = prepPangoLayout(); g_object_unref(LAYOUT); cairo_destroy(CAIRO); return LAYOUTSIZE / lastScale; } CBox STextImpl::getCharBox(size_t offset) { auto [CAIROSURFACE, CAIRO, LAYOUT, LAYOUTSIZE] = prepPangoLayout(); PangoRectangle rect; pango_layout_index_to_pos(LAYOUT, offset, &rect); CBox charBox = CBox{ sc(rect.x) / sc(PANGO_SCALE), sc(rect.y) / sc(PANGO_SCALE), sc(rect.width) / sc(PANGO_SCALE), sc(rect.height) / sc(PANGO_SCALE), } .scale(1.F / lastScale); g_object_unref(LAYOUT); cairo_destroy(CAIRO); return charBox; } std::optional STextImpl::vecToOffset(const Vector2D& vec) { auto [CAIROSURFACE, CAIRO, LAYOUT, LAYOUTSIZE] = prepPangoLayout(); auto pangoX = sc(vec.x * PANGO_SCALE), // pangoY = sc(vec.y * PANGO_SCALE); int index = 0, trailing = 0; pango_layout_xy_to_index(LAYOUT, pangoX, pangoY, &index, &trailing); g_object_unref(LAYOUT); cairo_destroy(CAIRO); if (index == -1) return std::nullopt; return index + trailing; } float STextImpl::getCursorPos(size_t offset) { if (offset >= parsedText.length()) return preferred.x; if (offset == 0) return 0; auto box = getCharBox(offset); return box.x; } float STextImpl::getCursorPos(const Hyprutils::Math::Vector2D& click) { return getCursorPos(vecToOffset(click).value_or(parsedText.length())); } Vector2D STextImpl::unscale(const Vector2D& x) { if (!self->impl->window) return x + Vector2D{self->impl->margin * 2, self->impl->margin * 2}; return (x + Vector2D{self->impl->margin * 2, self->impl->margin * 2}) / self->impl->window->scale(); } void STextImpl::scheduleTexRefresh() { if (data.async) { needsTexRefresh = true; return; } } void STextImpl::renderTex() { oldTex = tex; needsTexRefresh = false; ASSERT(!resource); tex.reset(); waitingForTex = true; lastScale = self->impl->window ? self->impl->window->scale() : 1.F; self->impl->damageEntire(); std::optional maxSize = data.clampSize.value_or(lastMaxSize).round(); if (maxSize == Vector2D{0, 0}) maxSize = std::nullopt; if (maxSize.has_value()) (*maxSize) *= lastScale; auto col = data.color(); resource = makeAtomicShared(CTextResource::STextResourceData{ .text = parsedText, .font = data.fontFamily, .fontSize = sc(std::round(lastFontSizeUnscaled * lastScale)), .color = CColor{CColor::SSRGB{.r = col.r, .g = col.g, .b = col.b}}, .align = data.align == HT_FONT_ALIGN_LEFT ? Hyprgraphics::CTextResource::TEXT_ALIGN_LEFT : (data.align == HT_FONT_ALIGN_CENTER ? Hyprgraphics::CTextResource::TEXT_ALIGN_CENTER : Hyprgraphics::CTextResource::TEXT_ALIGN_RIGHT), .maxSize = maxSize, .ellipsize = !data.noEllipsize && maxSize.has_value() && maxSize->y >= 0, .wrap = maxSize.has_value() && maxSize->x >= 0, }); if (Env::isTrace()) { const std::string TEXT_SHORT = data.text.size() > 20 ? data.text.substr(0, 20) + "..." : data.text; g_logger->log(HT_LOG_TRACE, "TextImpl: scheduling rendering of text \"{}\", with the following params:\nfont: {}, fontSize: {}, maxSize: {}, ellipsize: {}, wrap: {}", TEXT_SHORT, // data.fontFamily, // sc(std::round(lastFontSizeUnscaled * lastScale)), // maxSize.has_value() ? std::format("{}", *maxSize) : "", // !data.noEllipsize && maxSize.has_value() && maxSize->y >= 0, // maxSize.has_value() && maxSize->x >= 0 // ); } ASP resourceGeneric(resource); g_asyncResourceGatherer->enqueue(resourceGeneric); if (!data.async) { g_asyncResourceGatherer->await(resourceGeneric); postTexLoad(); } else { resource->m_events.finished.listenStatic([this, self = self->impl->self] { if (!self) return; g_backend->addIdle([this, self = self]() { if (!self) return; postTexLoad(); }); }); } } void STextImpl::postTexLoad() { if (!resource) return; ASP resourceGeneric(resource); size = resource->m_asset.pixelSize; tex = g_renderer->uploadTexture({.resource = resourceGeneric}); oldTex.reset(); if (self->impl->window) self->impl->window->scheduleReposition(self->impl->self); if (Env::isTrace()) { const std::string TEXT_SHORT = data.text.size() > 20 ? data.text.substr(0, 20) + "..." : data.text; if (size.x == 0 || size.y == 0 || !resourceGeneric->m_asset.cairoSurface) g_logger->log(HT_LOG_ERROR, "TextImpl: failed to render text \"{}\"!!!", TEXT_SHORT); else g_logger->log(HT_LOG_TRACE, "TextImpl: got a tex with size {} for text \"{}\"", size, TEXT_SHORT); } waitingForTex = false; newTex = true; resource.reset(); recheckTextBoxes(); if (data.callback) data.callback(); } static std::string formatColor(uint32_t col) { return std::format("#{0:08x}", ((col & 0x00ffffffu) << 8) | (col >> 24)); } void STextImpl::parseText() { parsedLinks.clear(); hoveredTextLink = nullptr; size_t lastTagClose = 0; const auto& ORIGINAL = data.text; std::string newString; while (true) { size_t tagOpen = ORIGINAL.find(" size_t needle = linkClose + 1; while (needle < ORIGINAL.size() && (ORIGINAL[needle] == ' ')) { needle++; } if (needle >= ORIGINAL.size() || ORIGINAL[needle] != '>') break; // broken tag size_t contentOpen = needle + 1; size_t contentClose = ORIGINAL.find("", contentOpen); if (contentClose == std::string::npos) break; // broken tag std::string replaceWith = std::format("{}", formatColor(g_palette->m_colors.linkText.getAsHex()), std::string_view{ORIGINAL}.substr(contentOpen, contentClose - contentOpen)); parsedLinks.emplace_back(STextLink{ .begin = newString.size(), .end = newString.size() + replaceWith.size(), .link = std::string{LINK}, }); newString += replaceWith; lastTagClose = contentClose + 4; } parsedText = std::move(newString); } void STextImpl::recheckTextBoxes() { for (auto& link : parsedLinks) { link.region.clear(); for (size_t i = link.begin; i < link.end + 1; ++i) { auto box = getCharBox(i); link.region.add(box); } } } void STextImpl::onMouseDown() { if (!hoveredTextLink) return; g_logger->log(HT_LOG_DEBUG, "STextImpl::onMouseDown: running link {}", hoveredTextLink->link); auto ret = DesktopMethods::openLink(hoveredTextLink->link); if (!ret) g_logger->log(HT_LOG_ERROR, "STextImpl::onMouseDown: failed to open link, ret: {}", ret.error()); } void STextImpl::onMouseMove() { hoveredTextLink = nullptr; for (auto& link : parsedLinks) { if (!link.region.containsPoint(lastCursorPos)) continue; hoveredTextLink = &link; break; } } hyprwm-hyprtoolkit-71515e8/src/element/text/Text.hpp000066400000000000000000000120531513450241200225460ustar00rootroot00000000000000 #pragma once #include #include #include #include #include "../../helpers/Memory.hpp" #include "../../core/InternalBackend.hpp" namespace Hyprtoolkit { struct STextData { std::string text; std::string fontFamily = g_palette ? g_palette->m_vars.fontFamily : "Sans Serif"; CFontSize fontSize{CFontSize::HT_FONT_TEXT}; eFontAlignment align = HT_FONT_ALIGN_LEFT; colorFn color = [] { return g_backend->getPalette()->m_colors.text; }; float a = 1.F; bool noEllipsize = false; std::optional clampSize; CDynamicSize size{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; std::function callback; // called after resource is loaded bool async = true; std::optional interactable; }; struct STextLink { uint64_t begin = 0, end = 0; std::string link; Hyprutils::Math::CRegion region; }; struct STextImpl { STextData data; std::string parsedText; std::vector parsedLinks; STextLink* hoveredTextLink = nullptr; WP self; size_t lastFontSizeUnscaled = 0; float lastScale = 1.F; bool needsTexRefresh = false, newTex = false; Hyprutils::Math::Vector2D lastMaxSize; SP tex; SP oldTex; // while loading a new one ASP resource; Hyprutils::Math::Vector2D size, preferred; Hyprutils::Math::Vector2D lastCursorPos; bool waitingForTex = false; Hyprutils::Math::Vector2D getTextSizePreferred(); Hyprutils::Math::CBox getCharBox(size_t offset); std::optional vecToOffset(const Hyprutils::Math::Vector2D& vec); float getCursorPos(size_t offset); float getCursorPos(const Hyprutils::Math::Vector2D& click); Hyprutils::Math::Vector2D unscale(const Hyprutils::Math::Vector2D& x); std::tuple, cairo_t*, PangoLayout*, Hyprutils::Math::Vector2D> prepPangoLayout(); void scheduleTexRefresh(); void renderTex(); void postTexLoad(); void parseText(); void recheckTextBoxes(); void onMouseDown(); void onMouseMove(); friend class CTextboxElement; friend struct STextboxImpl; }; } hyprwm-hyprtoolkit-71515e8/src/element/textbox/000077500000000000000000000000001513450241200216215ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/element/textbox/Builder.cpp000066400000000000000000000023761513450241200237230ustar00rootroot00000000000000#include "Textbox.hpp" using namespace Hyprtoolkit; SP CTextboxBuilder::begin() { SP p = SP(new CTextboxBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CTextboxBuilder::placeholder(std::string&& x) { m_data->placeholder = std::move(x); return m_self.lock(); } SP CTextboxBuilder::defaultText(std::string&& x) { m_data->text = std::move(x); return m_self.lock(); } SP CTextboxBuilder::onTextEdited(std::function, const std::string&)>&& x) { m_data->onTextEdited = std::move(x); return m_self.lock(); } SP CTextboxBuilder::size(CDynamicSize&& s) { m_data->size = std::move(s); return m_self.lock(); } SP CTextboxBuilder::multiline(bool x) { m_data->multiline = x; return m_self.lock(); } SP CTextboxBuilder::password(bool x) { m_data->password = x; return m_self.lock(); } SP CTextboxBuilder::commence() { if (m_element) { m_element->replaceData(*m_data); return m_element.lock(); } return CTextboxElement::create(*m_data); }hyprwm-hyprtoolkit-71515e8/src/element/textbox/Textbox.cpp000066400000000000000000000461001513450241200237630ustar00rootroot00000000000000#include "Textbox.hpp" #include #include #include #include #include #include "../../layout/Positioner.hpp" #include "../../renderer/Renderer.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../core/InternalBackend.hpp" #include "../Element.hpp" #include "../text/Text.hpp" #include "../../helpers/UTF8.hpp" using namespace Hyprtoolkit; SP CTextboxElement::create(const STextboxData& data) { auto p = SP(new CTextboxElement(data)); p->impl->self = p; p->m_impl->self = p; p->init(); return p; } CTextboxElement::CTextboxElement(const STextboxData& data) : IElement(), m_impl(makeUnique()) { m_impl->data = data; } void CTextboxElement::init() { m_impl->text = CTextBuilder::begin() ->text(std::string{m_impl->data.text}) ->color([] { return g_palette->m_colors.text; }) ->callback([this] { impl->window->scheduleReposition(impl->self); }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_impl->placeholder = CTextBuilder::begin() ->text(std::string{m_impl->data.placeholder}) ->color([] { return g_palette->m_colors.text.darken(0.4F); }) ->callback([this] { impl->window->scheduleReposition(impl->self); }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_impl->bg = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.base; }) ->rounding(g_palette->m_vars.smallRounding) ->borderColor([] { return g_palette->m_colors.alternateBase; }) ->borderThickness(1) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->commence(); m_impl->bgInnerCont = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})->commence(); m_impl->bgInnerCont->setMargin(3); m_impl->selectBgCont = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 0.7F}})->commence(); m_impl->selectBg = CRectangleBuilder::begin() ->color([] { auto x = g_palette->m_colors.accent.darken(0.4F); x.a = 0.5F; return x; }) ->commence(); m_impl->selectBgCont->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->selectBgCont->setPositionFlag(HT_POSITION_FLAG_VCENTER, true); m_impl->selectBg->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->selectBgCont->addChild(m_impl->selectBg); m_impl->cursorCont = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 0.8F}})->commence(); m_impl->cursor = CRectangleBuilder::begin()->color([] { return g_palette->m_colors.text; })->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})->commence(); m_impl->cursorCont->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->cursorCont->setPositionFlag(HT_POSITION_FLAG_VCENTER, true); m_impl->cursor->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->placeholder->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->placeholder->setPositionFlag(HT_POSITION_FLAG_VCENTER, true); m_impl->text->setPositionMode(HT_POSITION_ABSOLUTE); m_impl->text->setPositionFlag(HT_POSITION_FLAG_VCENTER, true); m_impl->listeners.mouseMove = impl->m_externalEvents.mouseMove.listen([this](Vector2D pos) { m_impl->lastCursorPos = pos; }); m_impl->listeners.mouseButton = impl->m_externalEvents.mouseButton.listen([this](Input::eMouseButton button, bool down) { if (down) m_impl->focusCursorAtClickedChar(); }); m_impl->listeners.enter = impl->m_externalEvents.keyboardEnter.listen([this] { m_impl->bgInnerCont->addChild(m_impl->cursorCont); impl->window->setIMTo(impl->position, m_impl->data.text, m_impl->inputState.cursor); m_impl->bg->rebuild()->borderColor([] { return g_palette->m_colors.alternateBase.brighten(0.5F); })->commence(); m_impl->focusCursorAtClickedChar(); }); m_impl->listeners.leave = impl->m_externalEvents.keyboardLeave.listen([this] { m_impl->bgInnerCont->removeChild(m_impl->cursorCont); impl->window->resetIM(); m_impl->bg->rebuild()->borderColor([] { return g_palette->m_colors.alternateBase; })->commence(); }); m_impl->listeners.key = impl->m_externalEvents.key.listen([this](const Input::SKeyboardKeyEvent& ev) { if (ev.xkbKeysym == XKB_KEY_BackSpace) { if (m_impl->inputState.cursor == 0) return; if (m_impl->hasSelect()) { m_impl->removeSelectedText(); m_impl->updateLabel(); return; } if ((ev.modMask & Input::HT_MODIFIER_CTRL_SHIFT) == Input::HT_MODIFIER_CTRL_SHIFT) m_impl->inputState.selectBegin = m_impl->moveLineBackwards(); else if (ev.modMask & Input::HT_MODIFIER_CTRL) m_impl->inputState.selectBegin = m_impl->moveWordBackwards(); else m_impl->inputState.selectBegin = m_impl->moveCharBackwards(); m_impl->inputState.selectEnd = m_impl->inputState.cursor; m_impl->inputState.cursor = m_impl->inputState.selectBegin; m_impl->removeSelectedText(); m_impl->updateLabel(); return; } if (ev.xkbKeysym == XKB_KEY_Delete) { if (m_impl->inputState.cursor == m_impl->data.text.length()) return; if (m_impl->hasSelect()) { m_impl->removeSelectedText(); m_impl->updateLabel(); return; } m_impl->inputState.selectBegin = m_impl->inputState.cursor; if ((ev.modMask & Input::HT_MODIFIER_CTRL_SHIFT) == Input::HT_MODIFIER_CTRL_SHIFT) m_impl->inputState.selectEnd = m_impl->moveLineForwards(); else if (ev.modMask & Input::HT_MODIFIER_CTRL) m_impl->inputState.selectEnd = m_impl->moveWordForwards(); else m_impl->inputState.selectEnd = m_impl->moveCharForwards(); m_impl->inputState.cursor = m_impl->inputState.selectBegin; m_impl->removeSelectedText(); m_impl->updateLabel(); return; } if (ev.xkbKeysym == XKB_KEY_Left) { if (m_impl->inputState.cursor == 0) return; const auto oldCursorPos = m_impl->inputState.cursor; if (ev.modMask & Input::HT_MODIFIER_CTRL) m_impl->inputState.cursor = m_impl->moveWordBackwards(); else m_impl->inputState.cursor = m_impl->moveCharBackwards(); if (ev.modMask & Input::HT_MODIFIER_SHIFT) { if (!m_impl->hasSelect()) { m_impl->inputState.selectBegin = m_impl->inputState.cursor; m_impl->inputState.selectEnd = oldCursorPos; } else { if (sc(oldCursorPos) == m_impl->inputState.selectEnd) m_impl->inputState.selectEnd = m_impl->inputState.cursor; else m_impl->inputState.selectBegin = m_impl->inputState.cursor; const auto selectBegin = std::min(m_impl->inputState.selectBegin, m_impl->inputState.selectEnd); const auto selectEnd = std::max(m_impl->inputState.selectBegin, m_impl->inputState.selectEnd); m_impl->inputState.selectBegin = selectBegin; m_impl->inputState.selectEnd = selectEnd; } m_impl->updateSelect(); } else if (m_impl->hasSelect()) { m_impl->inputState.cursor = m_impl->inputState.selectBegin; m_impl->clearSelect(); } m_impl->updateCursor(); return; } if (ev.xkbKeysym == XKB_KEY_Right) { if (m_impl->inputState.cursor == m_impl->data.text.length()) return; const auto oldCursorPos = m_impl->inputState.cursor; if (ev.modMask & Input::HT_MODIFIER_CTRL) m_impl->inputState.cursor = m_impl->moveWordForwards(); else m_impl->inputState.cursor = m_impl->moveCharForwards(); if (ev.modMask & Input::HT_MODIFIER_SHIFT) { if (!m_impl->hasSelect()) { m_impl->inputState.selectBegin = oldCursorPos; m_impl->inputState.selectEnd = m_impl->inputState.cursor; } else { if (sc(oldCursorPos) == m_impl->inputState.selectEnd) m_impl->inputState.selectEnd = m_impl->inputState.cursor; else m_impl->inputState.selectBegin = m_impl->inputState.cursor; const auto selectBegin = std::min(m_impl->inputState.selectBegin, m_impl->inputState.selectEnd); const auto selectEnd = std::max(m_impl->inputState.selectBegin, m_impl->inputState.selectEnd); m_impl->inputState.selectBegin = selectBegin; m_impl->inputState.selectEnd = selectEnd; } m_impl->updateSelect(); } else if (m_impl->hasSelect()) { m_impl->inputState.cursor = m_impl->inputState.selectEnd; m_impl->clearSelect(); } m_impl->updateCursor(); return; } if (ev.xkbKeysym == XKB_KEY_Home || ev.xkbKeysym == XKB_KEY_KP_Home) { m_impl->inputState.cursor = 0; m_impl->updateCursor(); m_impl->clearSelect(); return; } if (ev.xkbKeysym == XKB_KEY_End || ev.xkbKeysym == XKB_KEY_KP_End) { m_impl->inputState.cursor = m_impl->data.text.length(); m_impl->updateCursor(); m_impl->clearSelect(); return; } if (ev.xkbKeysym == XKB_KEY_Escape) { if (impl->window) impl->window->unfocusKeyboard(); m_impl->clearSelect(); return; } if ((ev.xkbKeysym == XKB_KEY_A || ev.xkbKeysym == XKB_KEY_a) && ev.modMask & Input::HT_MODIFIER_CTRL) { m_impl->inputState.selectBegin = 0; m_impl->inputState.selectEnd = m_impl->data.text.length(); m_impl->inputState.cursor = m_impl->inputState.selectEnd; m_impl->updateSelect(); m_impl->updateCursor(); return; } if (ev.utf8.empty()) return; if (ev.utf8 == "\n" && !m_impl->data.multiline) return; m_impl->removeSelectedText(); m_impl->data.text = m_impl->data.text.insert(m_impl->inputState.cursor, ev.utf8); m_impl->inputState.cursor += ev.utf8.length(); m_impl->updateLabel(); }); m_impl->placeholder->setMargin(1); m_impl->text->setMargin(1); addChild(m_impl->bg); m_impl->bg->addChild(m_impl->bgInnerCont); m_impl->cursorCont->addChild(m_impl->cursor); m_impl->bg->impl->clipChildren = true; m_impl->updateLabel(); impl->grouped = true; } std::string_view CTextboxElement::currentText() { return m_impl->data.text; } size_t CTextboxElement::cursorPos() const { return m_impl->inputState.cursor; } std::tuple CTextboxElement::selection() const { return {m_impl->inputState.selectBegin, m_impl->inputState.selectEnd}; } void STextboxImpl::updateLabel() { if (data.text.empty()) { bgInnerCont->removeChild(text); bgInnerCont->addChild(placeholder); } else { bgInnerCont->removeChild(placeholder); bgInnerCont->addChild(text); } if (!data.password) { auto fullLabel = inputState.imText.empty() ? // data.text : // data.text.insert(inputState.cursor, "" + inputState.imText + ""); text->rebuild()->text(std::move(fullLabel))->commence(); } else { std::string pwdText = ""; for (size_t i = 0; i < data.text.size(); ++i) { pwdText += '*'; } auto fullLabel = inputState.imText.empty() ? // pwdText : // pwdText.insert(inputState.cursor, "" + inputState.imText + ""); text->rebuild()->text(std::move(fullLabel))->commence(); } updateCursor(); if (data.onTextEdited) data.onTextEdited(self.lock(), data.text); } void CTextboxElement::imCommitNewText(const std::string& s) { m_impl->inputState.imText = s; m_impl->updateLabel(); } void CTextboxElement::imApplyText() { m_impl->data.text = m_impl->data.text.insert(m_impl->inputState.cursor, m_impl->inputState.imText); m_impl->inputState.cursor += m_impl->inputState.imText.length(); m_impl->inputState.imText.clear(); m_impl->updateLabel(); } void STextboxImpl::updateCursor() { inputState.cursor = std::clamp(inputState.cursor, (size_t)0, data.text.length()); const float WIDTH = text->m_impl->getCursorPos(inputState.cursor); cursor->setAbsolutePosition({ WIDTH, 0.F, }); if (self->impl->window) self->impl->window->setIMTo(self->impl->position.copy().translate({std::clamp(WIDTH, 0.F, sc(self->impl->position.w)), 0.F}), data.text, inputState.cursor); g_positioner->repositionNeeded(cursor); } void CTextboxElement::focus(bool focus) { if (!impl->window) return; impl->window->setKeyboardFocus(impl->self.lock()); } void CTextboxElement::paint() { ; } void STextboxImpl::clearSelect() { inputState.selectBegin = -1; inputState.selectEnd = -1; updateSelect(); } void STextboxImpl::updateSelect() { if (inputState.selectBegin < 0) { bgInnerCont->removeChild(selectBgCont); return; } float begin = text->m_impl->getCursorPos(inputState.selectBegin), // end = text->m_impl->getCursorPos(inputState.selectEnd); float width = end - begin; selectBg->rebuild()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_PERCENT, {width, 1.F}})->commence(); selectBg->setAbsolutePosition(Vector2D{begin, 0.F}); bgInnerCont->addChild(selectBgCont); } bool STextboxImpl::hasSelect() const { return inputState.selectBegin >= 0 && inputState.selectEnd >= 0 && inputState.selectBegin < inputState.selectEnd; } void STextboxImpl::removeSelectedText() { if (hasSelect()) { data.text = data.text.substr(0, inputState.selectBegin) + data.text.substr(inputState.selectEnd); inputState.cursor = inputState.selectBegin; clearSelect(); } } void STextboxImpl::focusCursorAtClickedChar() { inputState.cursor = text->m_impl->vecToOffset(lastCursorPos - (text->impl->position.pos() - self->impl->position.pos())).value_or(data.text.size()); updateCursor(); clearSelect(); } size_t STextboxImpl::moveLineBackwards() const { if (inputState.cursor == 0) return inputState.cursor; auto newlineBeforeCursor = data.text.find_last_of('\n', inputState.cursor); newlineBeforeCursor = newlineBeforeCursor == std::string::npos ? 0 : newlineBeforeCursor + 1; return newlineBeforeCursor; } size_t STextboxImpl::moveLineForwards() const { auto newlineAfterCursor = data.text.find_first_of('\n', inputState.cursor); newlineAfterCursor = newlineAfterCursor == std::string::npos ? data.text.length() : newlineAfterCursor; return newlineAfterCursor; } size_t STextboxImpl::moveWordBackwards() const { if (inputState.cursor == 0) return inputState.cursor; // ignore spaces that are right behind the cursor auto searchStartPos = data.text.find_last_not_of(' ', inputState.cursor - 1); searchStartPos = searchStartPos == std::string::npos ? 0 : searchStartPos; auto spaceBeforeCursor = data.text.find_last_of(' ', searchStartPos); spaceBeforeCursor = spaceBeforeCursor == std::string::npos ? 0 : spaceBeforeCursor + 1; return spaceBeforeCursor; } size_t STextboxImpl::moveWordForwards() const { // ignore spaces that are right in front of the cursor auto searchStartPos = data.text.find_first_not_of(' ', inputState.cursor); searchStartPos = searchStartPos == std::string::npos ? data.text.length() : searchStartPos + 1; auto spaceAfterCursor = data.text.find_first_of(' ', searchStartPos); spaceAfterCursor = spaceAfterCursor == std::string::npos ? data.text.length() : spaceAfterCursor; return spaceAfterCursor; } size_t STextboxImpl::moveCharBackwards() const { if (inputState.cursor == 0) return inputState.cursor; return inputState.cursor - UTF8::codepointLenBefore(data.text, inputState.cursor); } size_t STextboxImpl::moveCharForwards() const { return inputState.cursor + UTF8::codepointLen(&data.text[inputState.cursor], data.text.length() - inputState.cursor); } void CTextboxElement::reposition(const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize) { IElement::reposition(box); g_positioner->positionChildren(impl->self.lock()); } SP CTextboxElement::rebuild() { auto p = SP(new CTextboxBuilder()); p->m_self = p; p->m_data = makeUnique(m_impl->data); p->m_element = m_impl->self; return p; } void CTextboxElement::replaceData(const STextboxData& data) { const bool TEXTS_DIFFER = data.text != m_impl->data.text; m_impl->data = data; if (TEXTS_DIFFER) m_impl->updateLabel(); if (impl->window) impl->window->scheduleReposition(impl->self); } Hyprutils::Math::Vector2D CTextboxElement::size() { return impl->position.size(); } std::optional CTextboxElement::preferredSize(const Hyprutils::Math::Vector2D& parent) { return m_impl->data.size.calculate(parent); } std::optional CTextboxElement::minimumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return Vector2D{0, 0}; } std::optional CTextboxElement::maximumSize(const Hyprutils::Math::Vector2D& parent) { auto s = m_impl->data.size.calculate(parent); if (s.x != -1 && s.y != -1) return s; return std::nullopt; } bool CTextboxElement::acceptsMouseInput() { return true; } ePointerShape CTextboxElement::pointerShape() { return HT_POINTER_TEXT; } bool CTextboxElement::acceptsKeyboardInput() { return true; } bool CTextboxElement::positioningDependsOnChild() { return m_impl->data.size.hasAuto(); } hyprwm-hyprtoolkit-71515e8/src/element/textbox/Textbox.hpp000066400000000000000000000052411513450241200237710ustar00rootroot00000000000000#pragma once #include #include #include "../../helpers/Memory.hpp" using namespace Hyprutils::Signal; namespace Hyprtoolkit { class CRectangleElement; class CNullElement; class CTextElement; struct STextboxData { std::string text; std::string placeholder; std::function, const std::string&)> onTextEdited; bool multiline = true; bool password = false; CDynamicSize size{CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}; }; struct STextboxImpl { STextboxData data; WP self; SP bg; SP bgInnerCont; SP cursorCont; SP cursor; SP selectBgCont; SP selectBg; SP text; SP placeholder; bool active = false; struct { CHyprSignalListener key; CHyprSignalListener enter; CHyprSignalListener leave; CHyprSignalListener mouseMove; CHyprSignalListener mouseButton; } listeners; struct { size_t cursor = 0; std::string imText; ssize_t selectBegin = -1, selectEnd = -1; } inputState; void clearSelect(); void updateSelect(); bool hasSelect() const; void removeSelectedText(); void focusCursorAtClickedChar(); size_t moveLineBackwards() const; size_t moveLineForwards() const; size_t moveWordBackwards() const; size_t moveWordForwards() const; size_t moveCharBackwards() const; size_t moveCharForwards() const; void updateLabel(); void updateCursor(); Hyprutils::Math::Vector2D lastCursorPos; }; } hyprwm-hyprtoolkit-71515e8/src/helpers/000077500000000000000000000000001513450241200201355ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/helpers/DamageRing.cpp000066400000000000000000000022371513450241200226430ustar00rootroot00000000000000#include "DamageRing.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; void CDamageRing::setSize(const Vector2D& size_) { if (size_ == m_size) return; m_size = size_; damageEntire(); } bool CDamageRing::damage(CRegion&& rg) { rg.intersect(CBox{{}, m_size}); if (rg.empty()) return false; m_current.add(rg); return true; } void CDamageRing::damageEntire() { damage(CBox{{}, m_size}); } void CDamageRing::rotate() { m_previousIdx = (m_previousIdx + DAMAGE_RING_PREVIOUS_LEN - 1) % DAMAGE_RING_PREVIOUS_LEN; m_previous[m_previousIdx] = m_current; m_current.clear(); } CRegion CDamageRing::getBufferDamage(int age) { if (age <= 0 || age > DAMAGE_RING_PREVIOUS_LEN + 1) return CBox{{}, m_size}; CRegion damage = m_current; for (int i = 0; i < age - 1; ++i) { int j = (m_previousIdx + i) % DAMAGE_RING_PREVIOUS_LEN; damage.add(m_previous.at(j)); } // don't return a ludicrous amount of rects if (damage.getRects().size() > 8) return damage.getExtents(); return damage; } bool CDamageRing::hasChanged() { return !m_current.empty(); } hyprwm-hyprtoolkit-71515e8/src/helpers/DamageRing.hpp000066400000000000000000000016021513450241200226430ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit { constexpr static int DAMAGE_RING_PREVIOUS_LEN = 2; class CDamageRing { public: void setSize(const Hyprutils::Math::Vector2D& size_); bool damage(Hyprutils::Math::CRegion&& rg); void damageEntire(); void rotate(); Hyprutils::Math::CRegion getBufferDamage(int age); bool hasChanged(); private: Hyprutils::Math::Vector2D m_size; Hyprutils::Math::CRegion m_current; std::array m_previous; size_t m_previousIdx = 0; }; } hyprwm-hyprtoolkit-71515e8/src/helpers/Env.cpp000066400000000000000000000006711513450241200213750ustar00rootroot00000000000000#include "Env.hpp" #include #include using namespace Hyprtoolkit; using namespace Hyprtoolkit::Env; bool Hyprtoolkit::Env::envEnabled(const std::string& env) { auto ret = getenv(env.c_str()); if (!ret) return false; const std::string_view sv = ret; return !sv.empty() && sv != "0"; } bool Hyprtoolkit::Env::isTrace() { static bool TRACE = envEnabled("HT_TRACE"); return TRACE; } hyprwm-hyprtoolkit-71515e8/src/helpers/Env.hpp000066400000000000000000000002011513450241200213670ustar00rootroot00000000000000#pragma once #include namespace Hyprtoolkit::Env { bool envEnabled(const std::string& env); bool isTrace(); } hyprwm-hyprtoolkit-71515e8/src/helpers/Format.cpp000066400000000000000000000167531513450241200221050ustar00rootroot00000000000000#include "Format.hpp" #include #include #include #include #include #include #include "../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Memory; /* DRM formats are LE, while OGL is BE. The two primary formats will be flipped, so we will set flipRB which will later use swizzle to flip the red and blue channels. This will not work on GLES2, but I want to drop support for it one day anyways. */ inline const std::vector GLES3_FORMATS = { { .drmFormat = DRM_FORMAT_ARGB8888, .flipRB = true, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_BYTE, .withAlpha = true, .alphaStripped = DRM_FORMAT_XRGB8888, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_XRGB8888, .flipRB = true, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_BYTE, .withAlpha = false, .alphaStripped = DRM_FORMAT_XRGB8888, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_XBGR8888, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_BYTE, .withAlpha = false, .alphaStripped = DRM_FORMAT_XBGR8888, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_ABGR8888, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_BYTE, .withAlpha = true, .alphaStripped = DRM_FORMAT_XBGR8888, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_BGR888, .glFormat = GL_RGB, .glType = GL_UNSIGNED_BYTE, .withAlpha = false, .alphaStripped = DRM_FORMAT_BGR888, .bytesPerBlock = 3, }, { .drmFormat = DRM_FORMAT_RGBX4444, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_SHORT_4_4_4_4, .withAlpha = false, .alphaStripped = DRM_FORMAT_RGBX4444, .bytesPerBlock = 2, }, { .drmFormat = DRM_FORMAT_RGBA4444, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_SHORT_4_4_4_4, .withAlpha = true, .alphaStripped = DRM_FORMAT_RGBX4444, .bytesPerBlock = 2, }, { .drmFormat = DRM_FORMAT_RGBX5551, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_SHORT_5_5_5_1, .withAlpha = false, .alphaStripped = DRM_FORMAT_RGBX5551, .bytesPerBlock = 2, }, { .drmFormat = DRM_FORMAT_RGBA5551, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_SHORT_5_5_5_1, .withAlpha = true, .alphaStripped = DRM_FORMAT_RGBX5551, .bytesPerBlock = 2, }, { .drmFormat = DRM_FORMAT_RGB565, .glFormat = GL_RGB, .glType = GL_UNSIGNED_SHORT_5_6_5, .withAlpha = false, .alphaStripped = DRM_FORMAT_RGB565, .bytesPerBlock = 2, }, { .drmFormat = DRM_FORMAT_XBGR2101010, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_INT_2_10_10_10_REV, .withAlpha = false, .alphaStripped = DRM_FORMAT_XBGR2101010, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_ABGR2101010, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_INT_2_10_10_10_REV, .withAlpha = true, .alphaStripped = DRM_FORMAT_XBGR2101010, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_XRGB2101010, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_INT_2_10_10_10_REV, .withAlpha = false, .alphaStripped = DRM_FORMAT_XRGB2101010, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_ARGB2101010, .glFormat = GL_RGBA, .glType = GL_UNSIGNED_INT_2_10_10_10_REV, .withAlpha = true, .alphaStripped = DRM_FORMAT_XRGB2101010, .bytesPerBlock = 4, }, { .drmFormat = DRM_FORMAT_XBGR16161616F, .glFormat = GL_RGBA, .glType = GL_HALF_FLOAT, .withAlpha = false, .alphaStripped = DRM_FORMAT_XBGR16161616F, .bytesPerBlock = 8, }, { .drmFormat = DRM_FORMAT_ABGR16161616F, .glFormat = GL_RGBA, .glType = GL_HALF_FLOAT, .withAlpha = true, .alphaStripped = DRM_FORMAT_XBGR16161616F, .bytesPerBlock = 8, }, { .drmFormat = DRM_FORMAT_XBGR16161616, .glFormat = GL_RGBA16UI, .glType = GL_UNSIGNED_SHORT, .withAlpha = false, .alphaStripped = DRM_FORMAT_XBGR16161616, .bytesPerBlock = 8, }, { .drmFormat = DRM_FORMAT_ABGR16161616, .glFormat = GL_RGBA16UI, .glType = GL_UNSIGNED_SHORT, .withAlpha = true, .alphaStripped = DRM_FORMAT_XBGR16161616, .bytesPerBlock = 8, }, { .drmFormat = DRM_FORMAT_YVYU, .bytesPerBlock = 4, .blockSize = {2, 1}, }, { .drmFormat = DRM_FORMAT_VYUY, .bytesPerBlock = 4, .blockSize = {2, 1}, }, { .drmFormat = DRM_FORMAT_R8, .bytesPerBlock = 1, }, { .drmFormat = DRM_FORMAT_GR88, .bytesPerBlock = 2, }, { .drmFormat = DRM_FORMAT_RGB888, .bytesPerBlock = 3, }, { .drmFormat = DRM_FORMAT_BGR888, .bytesPerBlock = 3, }, { .drmFormat = DRM_FORMAT_RGBX4444, .bytesPerBlock = 2, }, }; const SPixelFormat* NFormatUtils::getPixelFormatFromDRM(DRMFormat drm) { for (auto const& fmt : GLES3_FORMATS) { if (fmt.drmFormat == drm) return &fmt; } return nullptr; } const SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha) { for (auto const& fmt : GLES3_FORMATS) { if (fmt.glFormat == sc(glFormat) && fmt.glType == sc(glType) && fmt.withAlpha == alpha) return &fmt; } return nullptr; } bool NFormatUtils::isFormatOpaque(DRMFormat drm) { const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm); if (!FMT) return false; return !FMT->withAlpha; } int NFormatUtils::pixelsPerBlock(const SPixelFormat* const fmt) { return fmt->blockSize.x * fmt->blockSize.y > 0 ? fmt->blockSize.x * fmt->blockSize.y : 1; } int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) { return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt)); } uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) { switch (drm) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; default: return GL_RGBA; } UNREACHABLE(); return GL_RGBA; } uint32_t NFormatUtils::glFormatToType(uint32_t gl) { return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; } std::string NFormatUtils::drmFormatName(DRMFormat drm) { auto n = drmGetFormatName(drm); std::string name = n; free(n); return name; } std::string NFormatUtils::drmModifierName(uint64_t mod) { auto n = drmGetFormatModifierName(mod); std::string name = n; free(n); return name; } hyprwm-hyprtoolkit-71515e8/src/helpers/Format.hpp000066400000000000000000000027401513450241200221010ustar00rootroot00000000000000#pragma once #include #include #include #include namespace Hyprtoolkit { using DRMFormat = uint32_t; using SHMFormat = uint32_t; struct SPixelFormat { DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ bool flipRB = false; int glInternalFormat = 0; int glFormat = 0; int glType = 0; bool withAlpha = true; DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ uint32_t bytesPerBlock = 0; Hyprutils::Math::Vector2D blockSize; }; using SDRMFormat = Aquamarine::SDRMFormat; namespace NFormatUtils { const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm); const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha); bool isFormatOpaque(DRMFormat drm); int pixelsPerBlock(const SPixelFormat* const fmt); int minStride(const SPixelFormat* const fmt, int32_t width); uint32_t drmFormatToGL(DRMFormat drm); uint32_t glFormatToType(uint32_t gl); std::string drmFormatName(DRMFormat drm); std::string drmModifierName(uint64_t mod); }; } hyprwm-hyprtoolkit-71515e8/src/helpers/Memory.hpp000066400000000000000000000004351513450241200221200ustar00rootroot00000000000000#pragma once #include #include #include using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer #define ASP CAtomicSharedPointerhyprwm-hyprtoolkit-71515e8/src/helpers/Signal.hpp000066400000000000000000000001321513450241200220570ustar00rootroot00000000000000#pragma once #include using namespace Hyprutils::Signal;hyprwm-hyprtoolkit-71515e8/src/helpers/Timer.cpp000066400000000000000000000017041513450241200217230ustar00rootroot00000000000000#include #include "Memory.hpp" using namespace Hyprtoolkit; CTimer::CTimer(std::chrono::steady_clock::duration timeout, std::function self, void* data)> cb_, void* data_, bool force) : m_cb(cb_), m_data(data_), m_allowForceUpdate(force) { m_expires = std::chrono::steady_clock::now() + timeout; } bool CTimer::passed() { return std::chrono::steady_clock::now() > m_expires; } void CTimer::cancel() { m_wasCancelled = true; } bool CTimer::cancelled() { return m_wasCancelled; } void CTimer::call(ASP self) { m_cb(self, m_data); } void CTimer::updateTimeout(std::chrono::steady_clock::duration timeout) { m_expires = std::chrono::steady_clock::now() + timeout; } float CTimer::leftMs() { return std::chrono::duration_cast(m_expires - std::chrono::steady_clock::now()).count(); } bool CTimer::canForceUpdate() { return m_allowForceUpdate; } hyprwm-hyprtoolkit-71515e8/src/helpers/UTF8.cpp000066400000000000000000000100641513450241200213700ustar00rootroot00000000000000#include "UTF8.hpp" #include using namespace Hyprtoolkit; using namespace Hyprtoolkit::UTF8; size_t UTF8::codepointLen(const char* utf8Char, size_t max) { size_t len = 1; while (len < max) { if (((*(utf8Char + len)) & 0xC0) == 0x80) { len++; continue; } break; } return len; } size_t UTF8::codepointLenBefore(const std::string& s, size_t offset) { const auto initialOffset = offset; while (true) { if (offset == 0) return 0; offset--; if ((s[offset] & 0xC0) != 0x80) break; } return initialOffset - offset; } size_t UTF8::length(const std::string& s) { if (s.empty()) return 0; const char* c = s.c_str(); size_t len = 0; auto endp = s.c_str() + s.length(); while (c < endp) { c += codepointLen(c, (endp - c)); len++; } return len; } /// Converts a byte index to a codepoint index. size_t UTF8::offsetToUTF8Len(const std::string& s, size_t offset) { if (s.empty()) return 0; const char* c = s.c_str(); size_t len = 0; auto endp = std::min(s.c_str() + s.length(), s.c_str() + offset); while (c < endp) { c += codepointLen(c, (endp - c)); len++; } return len; } /// Converts a codepoint index to a byte index. size_t UTF8::utf8ToOffset(const std::string& s, size_t utf8) { if (s.empty()) return 0; const char* c = s.c_str(); size_t len = 0; auto endp = s.c_str() + s.length(); while (c < endp) { if (len >= utf8) return c - s.c_str(); c += codepointLen(c, (endp - c)); len++; } return s.size(); } std::string UTF8::substr(const std::string& s, size_t start, size_t length) { if (s.empty() || length == 0) return ""; bool started = false; size_t byteStart = 0, byteEnd = std::string::npos; const char* c = s.c_str(); size_t len = 0; auto endp = s.c_str() + s.length(); while (c < endp) { if (len >= start && !started) { started = true; byteStart = c - s.c_str(); if (length == std::string::npos) break; len = 0; } c += codepointLen(c, endp - c); len++; if (len >= length && started) { byteEnd = c - s.c_str(); break; } } if (len == 0 || !started) return ""; if (byteEnd == std::string::npos) return s.substr(byteStart); return s.substr(byteStart, byteEnd - byteStart); } size_t UTF8::findFirstOf(const std::string& s, const std::string ch, size_t offset) { while (offset < s.length()) { const auto c = s.data() + offset; const auto cpLen = codepointLen(c, s.length() - offset); if (std::string_view{c, cpLen} == ch) return offset; offset += cpLen; } return std::string::npos; } size_t UTF8::findLastOf(const std::string& s, const std::string ch, size_t offset) { offset = std::min(offset, s.length()); while (offset > 0) { const auto cpLen = codepointLenBefore(s, offset); offset -= cpLen; if (std::string_view{&s[offset], cpLen} == ch) return offset; } return std::string::npos; } size_t UTF8::findFirstNotOf(const std::string& s, const std::string ch, size_t offset) { while (offset < s.length()) { const auto c = s.data() + offset; const auto cpLen = codepointLen(c, s.length() - offset); if (std::string_view{c, cpLen} != ch) return offset; offset += cpLen; } return std::string::npos; } size_t UTF8::findLastNotOf(const std::string& s, const std::string ch, size_t offset) { offset = std::min(offset, s.length()); while (offset > 0) { const auto cpLen = codepointLenBefore(s, offset); offset -= cpLen; if (std::string_view{&s[offset], cpLen} != ch) return offset; } return std::string::npos; } hyprwm-hyprtoolkit-71515e8/src/helpers/UTF8.hpp000066400000000000000000000015371513450241200214020ustar00rootroot00000000000000#pragma once #include namespace Hyprtoolkit::UTF8 { size_t codepointLen(const char* utf8Char, size_t max); size_t codepointLenBefore(const std::string& s, size_t offset); size_t length(const std::string&); size_t offsetToUTF8Len(const std::string&, size_t offset); size_t utf8ToOffset(const std::string&, size_t utf8); std::string substr(const std::string&, size_t start, size_t len = std::string::npos); size_t findFirstOf(const std::string&, const std::string ch, size_t offset = 0); size_t findLastOf(const std::string&, const std::string ch, size_t offset = std::string::npos); size_t findFirstNotOf(const std::string&, const std::string ch, size_t offset = 0); size_t findLastNotOf(const std::string&, const std::string ch, size_t offset = std::string::npos); } hyprwm-hyprtoolkit-71515e8/src/layout/000077500000000000000000000000001513450241200200105ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/layout/Positioner.cpp000066400000000000000000000076411513450241200226570ustar00rootroot00000000000000#include "Positioner.hpp" #include "../element/Element.hpp" #include "../window/ToolkitWindow.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; void CPositioner::position(SP element, const CBox& box, const Hyprutils::Math::Vector2D& maxSize) { initElementIfNeeded(element); // damage old box if (element->impl->window) element->impl->window->damage(element->impl->positionerData->baseBox); element->impl->positionerData->baseBox = box; element->reposition(box, maxSize); if (element->impl->window) element->impl->window->damage(box); } void CPositioner::positionChildren(SP element, const SRepositionData& data) { const auto C = element->impl->children; const auto BOX = element->impl->position.copy().translate(data.offset); // position children according to how they wanna be positioned for (const auto& c : C) { auto itemSize = c->preferredSize(BOX.size()); if (!itemSize) { // no size to base off of, just position CBox itemBox = BOX; if (data.growX) itemBox.w = 99999999; if (data.growY) itemBox.h = 99999999; position(c, itemBox); continue; } // it has a size, let's see what it wants. CBox itemBox = {BOX.pos(), *itemSize}; if (c->impl->positionMode == IElement::HT_POSITION_ABSOLUTE) { // apply position first, then centering if (c->impl->positionFlags & IElement::HT_POSITION_FLAG_LEFT) ; else if (c->impl->positionFlags & IElement::HT_POSITION_FLAG_RIGHT) { if (BOX.size().x > itemBox.size().x) itemBox.translate({(BOX.size() - itemBox.size()).x, 0.F}); } if (c->impl->positionFlags & IElement::HT_POSITION_FLAG_TOP) ; else if (c->impl->positionFlags & IElement::HT_POSITION_FLAG_BOTTOM) { if (BOX.size().y > itemBox.size().y) itemBox.translate({0.F, (BOX.size() - itemBox.size()).y}); } if (c->impl->positionFlags & IElement::HT_POSITION_FLAG_HCENTER) itemBox.translate(Vector2D{((BOX.size() - itemBox.size()) / 2.F).x, 0.F}); if (c->impl->positionFlags & IElement::HT_POSITION_FLAG_VCENTER) itemBox.translate(Vector2D{0.F, ((BOX.size() - itemBox.size()) / 2.F).y}); itemBox.translate(c->impl->absoluteOffset); } if (data.growX) itemBox.w = 99999999; if (data.growY) itemBox.h = 99999999; position(c, itemBox); } } void CPositioner::repositionNeeded(SP element, bool force) { if (!element->impl->parent) { // root el likely, check if (!element->impl->window || element != element->impl->window->m_rootElement) { initElementIfNeeded(element); position(element, element->impl->positionerData->baseBox); return; } // otherwise, max box position(element, {{}, (element->impl->window->pixelSize() / element->impl->window->scale()).round()}); return; } if (!element->impl->parent->impl->positionerData || element->impl->parent->impl->positionerData->baseBox.empty()) { if (force) { initElementIfNeeded(element); position(element, CBox{Vector2D{}, element->preferredSize(Vector2D{}).value_or(Vector2D{})}); } else if (element->impl->window) // full reflow needed element->impl->window->scheduleReposition(element->impl->window->m_rootElement); return; } position(element->impl->parent.lock(), element->impl->parent->impl->positionerData->baseBox); } void CPositioner::initElementIfNeeded(SP el) { if (el->impl->positionerData) return; el->impl->positionerData = makeUnique(); } hyprwm-hyprtoolkit-71515e8/src/layout/Positioner.hpp000066400000000000000000000016551513450241200226630ustar00rootroot00000000000000#pragma once #include #include #include "../helpers/Memory.hpp" namespace Hyprtoolkit { class IElement; struct SPositionerData { Hyprutils::Math::CBox baseBox; }; struct SRepositionData { Hyprutils::Math::Vector2D offset = {0, 0}; bool growX = false; bool growY = false; }; class CPositioner { public: void position(SP element, const Hyprutils::Math::CBox& box, const Hyprutils::Math::Vector2D& maxSize = {-1, -1}); void positionChildren(SP element, const SRepositionData& data = {}); void repositionNeeded(SP element, bool force = false); private: void initElementIfNeeded(SP element); size_t m_depth = 0; }; inline UP g_positioner = makeUnique(); }hyprwm-hyprtoolkit-71515e8/src/output/000077500000000000000000000000001513450241200200335ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/output/WaylandOutput.cpp000066400000000000000000000062771513450241200233730ustar00rootroot00000000000000#include "WaylandOutput.hpp" #include "../core/InternalBackend.hpp" #include "../helpers/Memory.hpp" using namespace Hyprtoolkit; static Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) { switch (t) { case WL_OUTPUT_TRANSFORM_NORMAL: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; case WL_OUTPUT_TRANSFORM_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_180; case WL_OUTPUT_TRANSFORM_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_90; case WL_OUTPUT_TRANSFORM_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_270; case WL_OUTPUT_TRANSFORM_FLIPPED: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED; case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180; case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270; case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90; default: break; } return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; } CWaylandOutput::CWaylandOutput(wl_proxy* wlResource, uint32_t id) : m_id(id), m_wlOutput(makeShared(wlResource)) { m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) { m_configuration.desc = description ? std::string{description} : ""; g_logger->log(HT_LOG_DEBUG, "wayland output {}: description {}", m_id, m_configuration.desc); }); m_wlOutput->setName([this](CCWlOutput* r, const char* name) { m_configuration.name = std::string{name} + m_configuration.name; m_configuration.port = std::string{name}; g_logger->log(HT_LOG_DEBUG, "wayland output {}: name {}", m_id, name); }); m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { m_configuration.scale = sc; }); m_wlOutput->setDone([this](CCWlOutput* r) { m_configuration.done = true; g_logger->log(HT_LOG_DEBUG, "wayland output {}: done", m_id); }); m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { // handle portrait mode and flipped cases if (m_configuration.transform % 2 == 1) m_configuration.size = {height, width}; else m_configuration.size = {width, height}; g_logger->log(HT_LOG_DEBUG, "wayland output {}: dimensions {}", m_id, m_configuration.size); }); m_wlOutput->setGeometry( [this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) { m_configuration.transform = wlTransformToHyprutils((wl_output_transform)transform_); g_logger->log(HT_LOG_DEBUG, "wayland output {}: make {} model {}", m_id, make ? make : "", model ? model : ""); }); } uint32_t CWaylandOutput::handle() { return m_id; } std::string CWaylandOutput::port() { return m_configuration.port; } std::string CWaylandOutput::desc() { return m_configuration.desc; } uint32_t CWaylandOutput::fps() { return m_configuration.fps; } hyprwm-hyprtoolkit-71515e8/src/output/WaylandOutput.hpp000066400000000000000000000026121513450241200233650ustar00rootroot00000000000000#pragma once #include "wayland.hpp" #include #include #include #include #include #include namespace Hyprtoolkit { class CWaylandOutput : public IOutput { public: CWaylandOutput(wl_proxy* wlResource, uint32_t id); ~CWaylandOutput() = default; virtual uint32_t handle(); virtual std::string port(); virtual std::string desc(); virtual uint32_t fps(); uint32_t m_id = 0; bool m_focused = false; Hyprutils::Memory::CSharedPointer m_wlOutput = nullptr; struct { bool done = false; Hyprutils::Math::eTransform transform = Hyprutils::Math::HYPRUTILS_TRANSFORM_NORMAL; Hyprutils::Math::Vector2D size; uint32_t fps = 60; uint32_t scale = 1; std::string name = ""; std::string port = ""; std::string desc = ""; } m_configuration; }; } hyprwm-hyprtoolkit-71515e8/src/palette/000077500000000000000000000000001513450241200201315ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/palette/Color.cpp000066400000000000000000000056221513450241200217200ustar00rootroot00000000000000#include #include "../Macros.hpp" #include "../helpers/Memory.hpp" #include using namespace Hyprtoolkit; using namespace Hyprutils::Memory; #define ALPHA(c) ((double)(((c) >> 24) & 0xff) / 255.0) #define RED(c) ((double)(((c) >> 16) & 0xff) / 255.0) #define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0) #define BLUE(c) ((double)(((c)) & 0xff) / 255.0) CHyprColor::CHyprColor() { ; } CHyprColor::CHyprColor(float r_, float g_, float b_, float a_) : r(r_), g(g_), b(b_), a(a_) { okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab(); } CHyprColor::CHyprColor(uint64_t hex) : r(RED(hex)), g(GREEN(hex)), b(BLUE(hex)), a(ALPHA(hex)) { okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab(); } CHyprColor::CHyprColor(const Hyprgraphics::CColor& color, float a_) : a(a_) { const auto SRGB = color.asRgb(); r = SRGB.r; g = SRGB.g; b = SRGB.b; okLab = color.asOkLab(); } bool CHyprColor::operator==(const CHyprColor& c2) const { return c2.r == r && c2.g == g && c2.b == b && c2.a == a; } CHyprColor CHyprColor::operator-(const CHyprColor& c2) const { RASSERT(false, "CHyprColor: - is a STUB"); return {}; } CHyprColor CHyprColor::operator+(const CHyprColor& c2) const { RASSERT(false, "CHyprColor: + is a STUB"); return {}; } CHyprColor CHyprColor::operator*(const float& c2) const { RASSERT(false, "CHyprColor: * is a STUB"); return {}; } uint32_t CHyprColor::getAsHex() const { return ((uint32_t)(a * 255.f) * 0x1000000) + ((uint32_t)(r * 255.f) * 0x10000) + ((uint32_t)(g * 255.f) * 0x100) + ((uint32_t)(b * 255.f) * 0x1); } Hyprgraphics::CColor::SSRGB CHyprColor::asRGB() const { return {.r = r, .g = g, .b = b}; } Hyprgraphics::CColor::SOkLab CHyprColor::asOkLab() const { return okLab; } Hyprgraphics::CColor::SHSL CHyprColor::asHSL() const { return Hyprgraphics::CColor(okLab).asHSL(); } CHyprColor CHyprColor::stripA() const { return {sc(r), sc(g), sc(b), 1.F}; } CHyprColor CHyprColor::brighten(float coeff) const { return {sc(r * (1.F + coeff)), sc(g * (1.F + coeff)), sc(b * (1.F + coeff)), sc(a)}; } CHyprColor CHyprColor::darken(float coeff) const { return {sc(r * (1.F - coeff)), sc(g * (1.F - coeff)), sc(b * (1.F - coeff)), sc(a)}; } CHyprColor CHyprColor::mix(const CHyprColor& with, float coeff) const { const auto C = std::clamp(coeff, 0.F, 1.F); const auto A = asOkLab(); const auto B = with.asOkLab(); return { Hyprgraphics::CColor::SOkLab{ .l = (A.l * (1.F - C)) + (B.l * C), .a = (A.a * (1.F - C)) + (B.a * C), .b = (A.b * (1.F - C)) + (B.b * C), }, sc((a * (1.F - C)) + (with.a * C)), }; } hyprwm-hyprtoolkit-71515e8/src/palette/ConfigManager.cpp000066400000000000000000000213061513450241200233370ustar00rootroot00000000000000#include "ConfigManager.hpp" #include #include "../core/InternalBackend.hpp" #include #include #include #include using namespace Hyprtoolkit; static Hyprlang::CParseResult handleSource(const char* c, const char* v) { const std::string VALUE = v; const std::string COMMAND = c; const auto RESULT = g_config->handleSource(COMMAND, VALUE); Hyprlang::CParseResult result; if (RESULT.has_value()) result.setError(RESULT.value().c_str()); return result; } static std::string absolutePath(const std::string& rawpath, const std::string& currentDir) { std::filesystem::path path(rawpath); // Handling where rawpath starts with '~' if (!rawpath.empty() && rawpath[0] == '~') { static const char* const ENVHOME = getenv("HOME"); path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); } // Handling e.g. ./, ../ if (path.is_relative()) return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path); else return std::filesystem::weakly_canonical(path); } CConfigManager::CConfigManager() : m_inotifyFd(inotify_init()) { // Initialize the configuration // Read file from default location // or from an explicit location given by user const auto CFGPATH = Hyprutils::Path::findConfig("hyprtoolkit").first.value_or(""); m_configPath = CFGPATH; m_config = makeUnique(CFGPATH.c_str(), Hyprlang::SConfigOptions{.allowMissingConfig = true}); m_config->addConfigValue("background", Hyprlang::INT{0xFF181818}); m_config->addConfigValue("base", Hyprlang::INT{0xFF202020}); m_config->addConfigValue("text", Hyprlang::INT{0xFFDADADA}); m_config->addConfigValue("alternate_base", Hyprlang::INT{0xFF272727}); m_config->addConfigValue("bright_text", Hyprlang::INT{0xFFFFDEDE}); m_config->addConfigValue("link_text", Hyprlang::INT{0xFF4EECF8}); m_config->addConfigValue("accent", Hyprlang::INT{0xFF00FFCC}); m_config->addConfigValue("accent_secondary", Hyprlang::INT{0xFF0099F0}); m_config->addConfigValue("rounding_large", Hyprlang::INT{10}); m_config->addConfigValue("rounding_small", Hyprlang::INT{5}); m_config->addConfigValue("h1_size", Hyprlang::INT{19}); m_config->addConfigValue("h2_size", Hyprlang::INT{15}); m_config->addConfigValue("h3_size", Hyprlang::INT{13}); m_config->addConfigValue("font_size", Hyprlang::INT{11}); m_config->addConfigValue("small_font_size", Hyprlang::INT{10}); m_config->addConfigValue("icon_theme", Hyprlang::STRING{""}); m_config->addConfigValue("font_family", Hyprlang::STRING{"Sans Serif"}); m_config->addConfigValue("font_family_monospace", Hyprlang::STRING{"monospace"}); m_config->registerHandler(&::handleSource, "source", {.allowFlags = false}); m_config->commence(); replantWatch(); } void CConfigManager::replantWatch() { for (const auto& w : m_watches) { inotify_rm_watch(m_inotifyFd.get(), w); } m_watches.clear(); m_watches.emplace_back(inotify_add_watch(m_inotifyFd.get(), m_configPath.c_str(), IN_MODIFY | IN_DONT_FOLLOW)); } void CConfigManager::parse() { const auto ERROR = m_config->parse(); if (ERROR.error) g_logger->log(HT_LOG_ERROR, "Error in config: {}", ERROR.getError()); } SP CConfigManager::getPalette() { auto p = CPalette::emptyPalette(); auto BACKGROUND = Hyprlang::CSimpleConfigValue(m_config.get(), "background"); auto BASE = Hyprlang::CSimpleConfigValue(m_config.get(), "base"); auto TEXT = Hyprlang::CSimpleConfigValue(m_config.get(), "text"); auto ALTERNATEBASE = Hyprlang::CSimpleConfigValue(m_config.get(), "alternate_base"); auto BRIGHTTEXT = Hyprlang::CSimpleConfigValue(m_config.get(), "bright_text"); auto LINK = Hyprlang::CSimpleConfigValue(m_config.get(), "link_text"); auto ACCENT = Hyprlang::CSimpleConfigValue(m_config.get(), "accent"); auto ACCENTSECONDARY = Hyprlang::CSimpleConfigValue(m_config.get(), "accent_secondary"); auto ROUNDINGLARGE = Hyprlang::CSimpleConfigValue(m_config.get(), "rounding_large"); auto ROUNDINGSMALL = Hyprlang::CSimpleConfigValue(m_config.get(), "rounding_small"); auto H1SIZE = Hyprlang::CSimpleConfigValue(m_config.get(), "h1_size"); auto H2SIZE = Hyprlang::CSimpleConfigValue(m_config.get(), "h2_size"); auto H3SIZE = Hyprlang::CSimpleConfigValue(m_config.get(), "h3_size"); auto FONTSIZE = Hyprlang::CSimpleConfigValue(m_config.get(), "font_size"); auto SMALLFONTSIZE = Hyprlang::CSimpleConfigValue(m_config.get(), "small_font_size"); auto ICONTHEME = Hyprlang::CSimpleConfigValue(m_config.get(), "icon_theme"); auto FONTFAMILY = Hyprlang::CSimpleConfigValue(m_config.get(), "font_family"); auto FONTFAMILYMONO = Hyprlang::CSimpleConfigValue(m_config.get(), "font_family_monospace"); p->m_colors.background = *BACKGROUND; p->m_colors.base = *BASE; p->m_colors.text = *TEXT; p->m_colors.alternateBase = *ALTERNATEBASE; p->m_colors.brightText = *BRIGHTTEXT; p->m_colors.linkText = *LINK; p->m_colors.accent = *ACCENT; p->m_colors.accentSecondary = *ACCENTSECONDARY; p->m_vars.bigRounding = *ROUNDINGLARGE; p->m_vars.smallRounding = *ROUNDINGSMALL; p->m_vars.h1Size = *H1SIZE; p->m_vars.h2Size = *H2SIZE; p->m_vars.h3Size = *H3SIZE; p->m_vars.fontSize = *FONTSIZE; p->m_vars.smallFontSize = *SMALLFONTSIZE; p->m_vars.iconTheme = *ICONTHEME; p->m_vars.fontFamily = *FONTFAMILY; p->m_vars.fontFamilyMonospace = *FONTFAMILYMONO; return p; } void CConfigManager::onInotifyEvent() { constexpr size_t BUFFER_SIZE = sizeof(inotify_event) + NAME_MAX + 1; alignas(inotify_event) std::array buffer = {}; const ssize_t bytesRead = read(m_inotifyFd.get(), buffer.data(), buffer.size()); if (bytesRead <= 0) return; for (size_t offset = 0; offset < sc(bytesRead);) { const auto* ev = rc(buffer.data() + offset); if (offset + sizeof(inotify_event) > sc(bytesRead)) { // err break; } if (offset + sizeof(inotify_event) + ev->len > sc(bytesRead)) { // err break; } offset += sizeof(inotify_event) + ev->len; } replantWatch(); parse(); } std::optional CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { if (rawpath.length() < 2) { g_logger->log(HT_LOG_ERROR, "source= path garbage"); return "source path " + rawpath + " bogus!"; } std::unique_ptr glob_buf{new glob_t, [](glob_t* g) { globfree(g); }}; memset(glob_buf.get(), 0, sizeof(glob_t)); const auto CURRENTDIR = std::filesystem::path(m_configCurrentPath).parent_path().string(); if (auto r = glob(absolutePath(rawpath, CURRENTDIR).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); g_logger->log(HT_LOG_ERROR, "{}", err); return err; } for (size_t i = 0; i < glob_buf->gl_pathc; i++) { const auto PATH = absolutePath(glob_buf->gl_pathv[i], CURRENTDIR); if (PATH.empty() || PATH == m_configCurrentPath) { g_logger->log(HT_LOG_WARNING, "source= skipping invalid path"); continue; } if (!std::filesystem::is_regular_file(PATH)) { if (std::filesystem::exists(PATH)) { g_logger->log(HT_LOG_WARNING, "source= skipping non-file {}", PATH); continue; } g_logger->log(HT_LOG_ERROR, "source= file doesnt exist"); return "source file " + PATH + " doesn't exist!"; } // allow for nested config parsing auto backupConfigPath = m_configCurrentPath; m_configCurrentPath = PATH; m_config->parseFile(PATH.c_str()); m_configCurrentPath = backupConfigPath; } return {}; } hyprwm-hyprtoolkit-71515e8/src/palette/ConfigManager.hpp000066400000000000000000000016151513450241200233450ustar00rootroot00000000000000#pragma once #include #include #include "../helpers/Memory.hpp" #include #include namespace Hyprtoolkit { class CPalette; class CConfigManager { public: // gets all the data from the config CConfigManager(); void parse(); void onInotifyEvent(); SP getPalette(); UP m_config; Hyprutils::OS::CFileDescriptor m_inotifyFd; std::vector m_watches; std::string m_configPath; std::string m_configCurrentPath; void replantWatch(); // std::optional handleSource(const std::string&, const std::string&); }; } hyprwm-hyprtoolkit-71515e8/src/palette/Palette.cpp000066400000000000000000000005761513450241200222430ustar00rootroot00000000000000#include #include "ConfigManager.hpp" #include "../helpers/Memory.hpp" #include "../core/InternalBackend.hpp" using namespace Hyprtoolkit; SP CPalette::palette() { auto x = g_config->getPalette(); x->m_isConfig = true; return x; } SP CPalette::emptyPalette() { return SP(new CPalette()); } hyprwm-hyprtoolkit-71515e8/src/renderer/000077500000000000000000000000001513450241200203015ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/renderer/Polygon.cpp000066400000000000000000000016301513450241200224340ustar00rootroot00000000000000#include "Polygon.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CPolygon::CPolygon(std::vector points) : m_points(points) { ; } CPolygon CPolygon::checkmark() { return CPolygon{std::vector{ {0.12F, 0.55F}, {0.25F, 0.39F}, {0.4F, 0.82F}, {0.4F, 0.57F}, {0.9F, 0.32F}, {0.78F, 0.17F}, }}; } CPolygon CPolygon::rangle() { return CPolygon{std::vector{ {0.25F, 0.15F}, {0.35F, 0.05F}, {0.6F, 0.5F}, {0.8F, 0.5F}, {0.25F, 0.85F}, {0.35F, 0.95F}, }}; } CPolygon CPolygon::langle() { auto p = rangle(); for (auto& v : p.m_points) { v.x = 1.F - v.x; } return p; } CPolygon CPolygon::dropdown() { return CPolygon{std::vector{ {0.1F, 0.3F}, {0.9F, 0.3F}, {0.5F, 0.7F}, }}; } hyprwm-hyprtoolkit-71515e8/src/renderer/Polygon.hpp000066400000000000000000000011551513450241200224430ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit { // Describes a polygon, from 0.0 - 1.0 coordinates. // Uses STRIP methodology like in OGL. class CPolygon { public: CPolygon(std::vector points); ~CPolygon() = default; static CPolygon checkmark(); static CPolygon rangle(); static CPolygon langle(); static CPolygon dropdown(); private: std::vector m_points; friend class COpenGLRenderer; friend class IRenderer; }; } hyprwm-hyprtoolkit-71515e8/src/renderer/Renderer.hpp000066400000000000000000000060221513450241200225600ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "../helpers/Memory.hpp" #include "Polygon.hpp" using namespace Hyprutils::Math; using namespace Hyprgraphics; namespace Hyprtoolkit { class IToolkitWindow; class IRendererTexture; class CSyncTimeline; class IRenderer { public: IRenderer() = default; virtual ~IRenderer() = default; struct SRectangleRenderData { CBox box; CHyprColor color; int rounding = 0; }; struct STextureData { ASP resource; eImageFitMode fitMode = IMAGE_FIT_MODE_STRETCH; }; struct STextureRenderData { CBox box; SP texture; float a = 1.F; int rounding = 0; }; struct SBorderRenderData { CBox box; CHyprColor color = {1, 1, 1, 1}; int rounding = 0; int thick = 0; }; struct SPolygonRenderData { CBox box; CHyprColor color = {1, 1, 1, 1}; CPolygon poly; }; struct SLineRenderData { CBox box; const std::vector points; CHyprColor color = {1, 1, 1, 1}; int thick = 2; }; virtual void beginRendering(SP window, SP buf) = 0; virtual void render(bool ignoreSync = false) = 0; virtual void endRendering() = 0; virtual void renderRectangle(const SRectangleRenderData& data) = 0; virtual SP uploadTexture(const STextureData& data) = 0; virtual void renderTexture(const STextureRenderData& data) = 0; virtual void renderBorder(const SBorderRenderData& data) = 0; virtual void renderPolygon(const SPolygonRenderData& data) = 0; virtual void renderLine(const SLineRenderData& data) = 0; virtual void signalRenderPoint(SP timeline) = 0; virtual SP exportSync(SP buf) = 0; virtual bool explicitSyncSupported() = 0; }; inline SP g_renderer; } hyprwm-hyprtoolkit-71515e8/src/renderer/RendererTexture.hpp000066400000000000000000000012101513450241200241330ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprtoolkit { class IRendererTexture { public: IRendererTexture() = default; virtual ~IRendererTexture() = default; enum eTextureType : uint8_t { TEXTURE_GL, }; virtual size_t id() = 0; virtual eTextureType type() = 0; virtual void destroy() = 0; virtual eImageFitMode fitMode() = 0; virtual Hyprutils::Math::Vector2D size() = 0; }; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/000077500000000000000000000000001513450241200207035ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Framebuffer.cpp000066400000000000000000000054321513450241200236370ustar00rootroot00000000000000#include "Framebuffer.hpp" #include "OpenGL.hpp" #include "GLTexture.hpp" #include "../../core/InternalBackend.hpp" #include "GL.hpp" #include #include "../../helpers/Format.hpp" #include "../../Macros.hpp" using namespace Hyprtoolkit; CFramebuffer::CFramebuffer() { ; } bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { bool firstAlloc = false; RASSERT((w > 0 && h > 0), "cannot alloc a FB with negative / zero size! (attempted {}x{})", w, h); uint32_t glFormat = NFormatUtils::drmFormatToGL(drmFormat); uint32_t glType = NFormatUtils::glFormatToType(glFormat); if (drmFormat != m_drmFormat || m_size != Vector2D{w, h}) release(); m_drmFormat = drmFormat; if (!m_tex) { m_tex = makeShared(); m_tex->allocate(); m_tex->bind(); glTexParameteri(m_tex->m_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_tex->m_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(m_tex->m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(m_tex->m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); firstAlloc = true; } if (!m_fbAllocated) { glGenFramebuffers(1, &m_fb); m_fbAllocated = true; firstAlloc = true; } if (firstAlloc || m_size != Vector2D(w, h)) { m_tex->bind(); glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_fb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0); auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc(glGetError())); } glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); m_size = Vector2D(w, h); return true; } void CFramebuffer::bind() { GLCALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb)); } void CFramebuffer::unbind() { GLCALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)); } void CFramebuffer::release() { if (m_fbAllocated) { glBindFramebuffer(GL_FRAMEBUFFER, m_fb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &m_fb); m_fbAllocated = false; m_fb = 0; } if (m_tex) m_tex.reset(); m_size = Vector2D(); } CFramebuffer::~CFramebuffer() { release(); } bool CFramebuffer::isAllocated() { return m_fbAllocated && m_tex; } SP CFramebuffer::getTexture() { return m_tex; } GLuint CFramebuffer::getFBID() { return m_fbAllocated ? m_fb : 0; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Framebuffer.hpp000066400000000000000000000015241513450241200236420ustar00rootroot00000000000000#pragma once #include "GLTexture.hpp" namespace Hyprtoolkit { class CFramebuffer { public: CFramebuffer(); ~CFramebuffer(); bool alloc(int w, int h, uint32_t format = GL_RGBA); void bind(); void unbind(); void release(); void reset(); bool isAllocated(); SP getTexture(); GLuint getFBID(); Hyprutils::Math::Vector2D m_size; uint32_t m_drmFormat = 0 /* DRM_FORMAT_INVALID */; private: SP m_tex; GLuint m_fb = -1; bool m_fbAllocated = false; friend class CRenderbuffer; }; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/GL.hpp000066400000000000000000000030421513450241200217150ustar00rootroot00000000000000#pragma once #include #ifndef HYPRTOOLKIT_DEBUG #define GLCALL(__CALL__) __CALL__; #else #define GLCALL(__CALL__) \ { \ __CALL__; \ auto err = glGetError(); \ if (err != GL_NO_ERROR) { \ g_logger->log(HT_LOG_ERROR, "[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err); \ } \ } #endifhyprwm-hyprtoolkit-71515e8/src/renderer/gl/GLTexture.cpp000066400000000000000000000052551513450241200233010ustar00rootroot00000000000000#include "GLTexture.hpp" #include "OpenGL.hpp" #include "../../core/InternalBackend.hpp" using namespace Hyprtoolkit; CGLTexture::CGLTexture(ASP resource) { if (resource->m_ready) { m_resource = resource; upload(); return; } // not ready yet, add a timer when it is and do it // FIXME: could UAF. Maybe keep wref? resource->m_events.finished.listenStatic([this, resource] { g_backend->addIdle([this, resource]() { m_resource = resource; upload(); }); }); } CGLTexture::CGLTexture() { ; } CGLTexture::~CGLTexture() { destroy(); } void CGLTexture::upload() { const cairo_status_t SURFACESTATUS = (cairo_status_t)m_resource->m_asset.cairoSurface->status(); const auto CAIROFORMAT = cairo_image_surface_get_format(m_resource->m_asset.cairoSurface->cairo()); const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; allocate(); if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) { g_logger->log(HT_LOG_ERROR, "Resource {} invalid: failed to load, renderer will ignore"); m_type = TEXTURE_INVALID; return; } m_type = TEXTURE_RGBA; m_size = m_resource->m_asset.pixelSize; GLCALL(glBindTexture(GL_TEXTURE_2D, m_texID)); GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE)); GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED)); } GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, m_size.x, m_size.y, 0, glFormat, glType, m_resource->m_asset.cairoSurface->data())); m_resource.reset(); } size_t CGLTexture::id() { return m_texID; } IRendererTexture::eTextureType CGLTexture::type() { return TEXTURE_GL; } void CGLTexture::destroy() { if (g_openGL) g_openGL->makeEGLCurrent(); if (m_allocated) { GLCALL(glDeleteTextures(1, &m_texID)); m_texID = 0; } m_allocated = false; } void CGLTexture::allocate() { if (!m_allocated) GLCALL(glGenTextures(1, &m_texID)); m_allocated = true; } void CGLTexture::bind() { GLCALL(glBindTexture(m_target, m_texID)); } eImageFitMode CGLTexture::fitMode() { return m_fitMode; } Vector2D CGLTexture::size() { return m_size; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/GLTexture.hpp000066400000000000000000000027551513450241200233100ustar00rootroot00000000000000#pragma once #include #include #include "GL.hpp" #include #include "../RendererTexture.hpp" #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { enum eGLTextureType : uint8_t { TEXTURE_INVALID, // Invalid TEXTURE_RGBA, // 4 channels TEXTURE_RGBX, // discard A TEXTURE_EXTERNAL, // EGLImage }; class CTimer; class CGLTexture : public IRendererTexture { public: CGLTexture(ASP); CGLTexture(); virtual ~CGLTexture(); virtual size_t id(); virtual eTextureType type(); virtual void destroy(); virtual eImageFitMode fitMode(); virtual Hyprutils::Math::Vector2D size(); eGLTextureType m_type = TEXTURE_RGBA; GLenum m_target = GL_TEXTURE_2D; bool m_allocated = false; GLuint m_texID = 0; eImageFitMode m_fitMode = IMAGE_FIT_MODE_STRETCH; Hyprutils::Math::Vector2D m_size = {}; ASP m_resource; void upload(); void allocate(); void bind(); }; };hyprwm-hyprtoolkit-71515e8/src/renderer/gl/OpenGL.cpp000066400000000000000000001224631513450241200225430ustar00rootroot00000000000000#include "OpenGL.hpp" #include "../../window/ToolkitWindow.hpp" #include "../../Macros.hpp" #include "../../core/InternalBackend.hpp" #include "../../element/Element.hpp" #include "../sync/SyncTimeline.hpp" #include "./shaders/Shaders.hpp" #include "GLTexture.hpp" #include "Renderbuffer.hpp" #include "Sync.hpp" #include #include #include #include #include #include using namespace Hyprtoolkit; using namespace Hyprutils::OS; using namespace Hyprutils::String; inline const float fullVerts[] = { 1, 0, // top right 0, 0, // top left 1, 1, // bottom right 0, 1, // bottom left }; static enum Hyprtoolkit::eLogLevel eglLogToLevel(EGLint type) { switch (type) { case EGL_DEBUG_MSG_CRITICAL_KHR: return HT_LOG_CRITICAL; case EGL_DEBUG_MSG_ERROR_KHR: return HT_LOG_ERROR; case EGL_DEBUG_MSG_WARN_KHR: return HT_LOG_WARNING; case EGL_DEBUG_MSG_INFO_KHR: return HT_LOG_DEBUG; default: return HT_LOG_DEBUG; } } static const char* eglErrorToString(EGLint error) { switch (error) { case EGL_SUCCESS: return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; case EGL_BAD_DEVICE_EXT: return "EGL_BAD_DEVICE_EXT"; case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; } return "Unknown"; } static void eglLog(EGLenum error, const char* command, EGLint type, EGLLabelKHR thread, EGLLabelKHR obj, const char* msg) { g_logger->log(eglLogToLevel(type), "[EGL] Command {} errored out with {} (0x{}): {}", command, eglErrorToString(error), error, msg); } static GLuint compileShader(const GLuint& type, std::string src) { auto shader = glCreateShader(type); auto shaderSource = src.c_str(); glShaderSource(shader, 1, &shaderSource, nullptr); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); return shader; } static GLuint createProgram(const std::string& vert, const std::string& frag) { auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert); RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert); auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag); RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag); auto prog = glCreateProgram(); glAttachShader(prog, vertCompiled); glAttachShader(prog, fragCompiled); glLinkProgram(prog); glDetachShader(prog, vertCompiled); glDetachShader(prog, fragCompiled); glDeleteShader(vertCompiled); glDeleteShader(fragCompiled); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); return prog; } static void glMessageCallbackA(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { if (type != GL_DEBUG_TYPE_ERROR) return; g_logger->log(Hyprtoolkit::HT_LOG_DEBUG, "[gl] {}", (const char*)message); } static inline void loadGLProc(void* pProc, const char* name) { void* proc = rc(eglGetProcAddress(name)); if (proc == nullptr) { g_logger->log(Hyprtoolkit::HT_LOG_CRITICAL, "[GL] eglGetProcAddress({}) failed", name); abort(); } *sc(pProc) = proc; } static int openRenderNode(int drmFd) { auto renderName = drmGetRenderDeviceNameFromFd(drmFd); if (!renderName) { // This can happen on split render/display platforms, fallback to // primary node renderName = drmGetPrimaryDeviceNameFromFd(drmFd); if (!renderName) { g_logger->log(Hyprtoolkit::HT_LOG_ERROR, "drmGetPrimaryDeviceNameFromFd failed"); return -1; } g_logger->log(HT_LOG_DEBUG, "DRM dev {} has no render node, falling back to primary", renderName); drmVersion* render_version = drmGetVersion(drmFd); if (render_version && render_version->name) { g_logger->log(HT_LOG_DEBUG, "DRM dev versionName", render_version->name); if (strcmp(render_version->name, "evdi") == 0) { free(renderName); renderName = strdup("/dev/dri/card0"); } drmFreeVersion(render_version); } } g_logger->log(HT_LOG_DEBUG, "openRenderNode got drm device {}", renderName); int renderFD = open(renderName, O_RDWR | O_CLOEXEC); if (renderFD < 0) g_logger->log(HT_LOG_ERROR, "openRenderNode failed to open drm device {}", renderName); free(renderName); return renderFD; } void COpenGLRenderer::initEGL(bool gbm) { std::vector attrs; if (m_exts.KHR_display_reference) { attrs.push_back(EGL_TRACK_REFERENCES_KHR); attrs.push_back(EGL_TRUE); } attrs.push_back(EGL_NONE); m_eglDisplay = m_proc.eglGetPlatformDisplayEXT(gbm ? EGL_PLATFORM_GBM_KHR : EGL_PLATFORM_DEVICE_EXT, gbm ? m_gbmDevice : m_eglDevice, attrs.data()); if (m_eglDisplay == EGL_NO_DISPLAY) RASSERT(false, "EGL: failed to create a platform display"); attrs.clear(); EGLint major, minor; if (eglInitialize(m_eglDisplay, &major, &minor) == EGL_FALSE) RASSERT(false, "EGL: failed to initialize a platform display"); const std::string EGLEXTENSIONS = eglQueryString(m_eglDisplay, EGL_EXTENSIONS); m_exts.IMG_context_priority = EGLEXTENSIONS.contains("IMG_context_priority"); m_exts.EXT_create_context_robustness = EGLEXTENSIONS.contains("EXT_create_context_robustness"); m_exts.EXT_image_dma_buf_import = EGLEXTENSIONS.contains("EXT_image_dma_buf_import"); m_exts.EXT_image_dma_buf_import_modifiers = EGLEXTENSIONS.contains("EXT_image_dma_buf_import_modifiers"); m_exts.EGL_ANDROID_native_fence_sync_ext = EGLEXTENSIONS.contains("EGL_ANDROID_native_fence_sync"); if (m_exts.IMG_context_priority) { g_logger->log(HT_LOG_DEBUG, "EGL: IMG_context_priority supported, requesting high"); attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG); } if (m_exts.EXT_create_context_robustness) { g_logger->log(HT_LOG_DEBUG, "EGL: EXT_create_context_robustness supported, requesting lose on reset"); attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT); attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT); } auto attrsNoVer = attrs; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); attrs.push_back(3); attrs.push_back(EGL_CONTEXT_MINOR_VERSION); attrs.push_back(0); attrs.push_back(EGL_NONE); m_eglContext = eglCreateContext(m_eglDisplay, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (m_eglContext == EGL_NO_CONTEXT) { RASSERT(false, "EGL: failed to create a context"); } if (m_exts.IMG_context_priority) { EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) g_logger->log(HT_LOG_ERROR, "EGL: Failed to obtain a high priority context"); else g_logger->log(HT_LOG_DEBUG, "EGL: Got a high priority context"); } eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext); } EGLImageKHR COpenGLRenderer::createEGLImage(const Aquamarine::SDMABUFAttrs& attrs) { std::array attribs; size_t idx = 0; attribs[idx++] = EGL_WIDTH; attribs[idx++] = attrs.size.x; attribs[idx++] = EGL_HEIGHT; attribs[idx++] = attrs.size.y; attribs[idx++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[idx++] = attrs.format; struct { EGLint fd; EGLint offset; EGLint pitch; EGLint modlo; EGLint modhi; } attrNames[4] = { {EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT}, {EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT}, {EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT}, {EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}}; for (int i = 0; i < attrs.planes; ++i) { attribs[idx++] = attrNames[i].fd; attribs[idx++] = attrs.fds[i]; attribs[idx++] = attrNames[i].offset; attribs[idx++] = attrs.offsets[i]; attribs[idx++] = attrNames[i].pitch; attribs[idx++] = attrs.strides[i]; if (m_hasModifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { attribs[idx++] = attrNames[i].modlo; attribs[idx++] = sc(attrs.modifier & 0xFFFFFFFF); attribs[idx++] = attrNames[i].modhi; attribs[idx++] = sc(attrs.modifier >> 32); } } attribs[idx++] = EGL_IMAGE_PRESERVED_KHR; attribs[idx++] = EGL_TRUE; attribs[idx++] = EGL_NONE; RASSERT(idx <= attribs.size(), "createEglImage: attribs array out of bounds."); EGLImageKHR image = m_proc.eglCreateImageKHR(m_eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, rc(attribs.data())); if (image == EGL_NO_IMAGE_KHR) { g_logger->log(HT_LOG_ERROR, "EGL: EGLCreateImageKHR failed: {}", eglGetError()); return EGL_NO_IMAGE_KHR; } return image; } static bool drmDeviceHasName(const drmDevice* device, const std::string& name) { for (size_t i = 0; i < DRM_NODE_MAX; i++) { if (!(device->available_nodes & (1 << i))) continue; if (device->nodes[i] == name) return true; } return false; } EGLDeviceEXT COpenGLRenderer::eglDeviceFromDRMFD(int drmFD) { EGLint nDevices = 0; if (!m_proc.eglQueryDevicesEXT(0, nullptr, &nDevices)) { g_logger->log(HT_LOG_ERROR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed"); return EGL_NO_DEVICE_EXT; } if (nDevices <= 0) { g_logger->log(HT_LOG_ERROR, "eglDeviceFromDRMFD: no devices"); return EGL_NO_DEVICE_EXT; } std::vector devices; devices.resize(nDevices); if (!m_proc.eglQueryDevicesEXT(nDevices, devices.data(), &nDevices)) { g_logger->log(HT_LOG_ERROR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed (2)"); return EGL_NO_DEVICE_EXT; } drmDevice* drmDev = nullptr; if (int ret = drmGetDevice(drmFD, &drmDev); ret < 0) { g_logger->log(HT_LOG_ERROR, "eglDeviceFromDRMFD: drmGetDevice failed"); return EGL_NO_DEVICE_EXT; } for (auto const& d : devices) { auto devName = m_proc.eglQueryDeviceStringEXT(d, EGL_DRM_DEVICE_FILE_EXT); if (!devName) continue; if (drmDeviceHasName(drmDev, devName)) { g_logger->log(HT_LOG_DEBUG, "eglDeviceFromDRMFD: Using device {}", devName); drmFreeDevice(&drmDev); return d; } } drmFreeDevice(&drmDev); g_logger->log(HT_LOG_DEBUG, "eglDeviceFromDRMFD: No drm devices found"); return EGL_NO_DEVICE_EXT; } void COpenGLRenderer::makeEGLCurrent() { if (eglGetCurrentContext() != m_eglContext) eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext); } void COpenGLRenderer::unsetEGL() { eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } static std::string loadShader(const std::string& filename) { if (SHADERS.contains(filename)) return SHADERS.at(filename); throw std::runtime_error(std::format("Couldn't load shader {}", filename)); } static void loadShaderInclude(const std::string& filename, std::map& includes) { includes.insert({filename, loadShader(filename)}); } static void processShaderIncludes(std::string& source, const std::map& includes) { for (auto it = includes.begin(); it != includes.end(); ++it) { replaceInString(source, "#include \"" + it->first + "\"", it->second); } } static std::string processShader(const std::string& filename, const std::map& includes) { auto source = loadShader(filename); processShaderIncludes(source, includes); return source; } COpenGLRenderer::COpenGLRenderer(int drmFD) : m_drmFD(drmFD) { const std::string EGLEXTENSIONS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); g_logger->log(HT_LOG_DEBUG, "Supported EGL global extensions: ({}) {}", std::ranges::count(EGLEXTENSIONS, ' '), EGLEXTENSIONS); m_exts.KHR_display_reference = EGLEXTENSIONS.contains("KHR_display_reference"); loadGLProc(&m_proc.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES"); loadGLProc(&m_proc.eglCreateImageKHR, "eglCreateImageKHR"); loadGLProc(&m_proc.eglDestroyImageKHR, "eglDestroyImageKHR"); loadGLProc(&m_proc.eglQueryDmaBufFormatsEXT, "eglQueryDmaBufFormatsEXT"); loadGLProc(&m_proc.eglQueryDmaBufModifiersEXT, "eglQueryDmaBufModifiersEXT"); loadGLProc(&m_proc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); loadGLProc(&m_proc.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT"); loadGLProc(&m_proc.eglCreateSyncKHR, "eglCreateSyncKHR"); loadGLProc(&m_proc.eglDestroySyncKHR, "eglDestroySyncKHR"); loadGLProc(&m_proc.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID"); loadGLProc(&m_proc.eglWaitSyncKHR, "eglWaitSyncKHR"); RASSERT(m_proc.eglCreateSyncKHR, "Display driver doesn't support eglCreateSyncKHR"); RASSERT(m_proc.eglDupNativeFenceFDANDROID, "Display driver doesn't support eglDupNativeFenceFDANDROID"); RASSERT(m_proc.eglWaitSyncKHR, "Display driver doesn't support eglWaitSyncKHR"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration")) loadGLProc(&m_proc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_query")) { loadGLProc(&m_proc.eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT"); loadGLProc(&m_proc.eglQueryDisplayAttribEXT, "eglQueryDisplayAttribEXT"); } if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); static const EGLAttrib debugAttrs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, }; m_proc.eglDebugMessageControlKHR(::eglLog, debugAttrs); } RASSERT(eglBindAPI(EGL_OPENGL_ES_API) != EGL_FALSE, "Couldn't bind to EGL's opengl ES API. This means your gpu driver f'd up. This is not a hyprland issue."); bool success = false; if (EGLEXTENSIONS.contains("EXT_platform_device") || !m_proc.eglQueryDevicesEXT || !m_proc.eglQueryDeviceStringEXT) { m_eglDevice = eglDeviceFromDRMFD(drmFD); if (m_eglDevice != EGL_NO_DEVICE_EXT) { success = true; initEGL(false); } } if (!success) { g_logger->log(HT_LOG_WARNING, "EGL: EXT_platform_device or EGL_EXT_device_query not supported, using gbm"); if (EGLEXTENSIONS.contains("KHR_platform_gbm")) { success = true; m_gbmFD = CFileDescriptor{openRenderNode(drmFD)}; if (!m_gbmFD.isValid()) RASSERT(false, "Couldn't open a gbm fd"); m_gbmDevice = gbm_create_device(m_gbmFD.get()); if (!m_gbmDevice) RASSERT(false, "Couldn't open a gbm device"); initEGL(true); } } RASSERT(success, "EGL does not support KHR_platform_gbm or EXT_platform_device, this is an issue with your gpu driver."); auto* const EXTENSIONS = rc(glGetString(GL_EXTENSIONS)); RASSERT(EXTENSIONS, "Couldn't retrieve openGL extensions!"); #if defined(__linux__) auto syncObjSupport = [](auto fd) { if (fd < 0) return false; uint64_t cap = 0; int ret = drmGetCap(fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap); return ret == 0 && cap != 0; }; m_syncobjSupported = syncObjSupport(m_drmFD); g_logger->log(HT_LOG_DEBUG, "DRM syncobj timeline support: {}", m_syncobjSupported ? "yes" : "no"); #else g_logger->log(HT_LOG_DEBUG, "DRM syncobj timeline support: no (not linux)"); #endif #ifdef HYPRTOOLKIT_DEBUG glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(glMessageCallbackA, nullptr); #endif std::map includes; loadShaderInclude("rounding.glsl", includes); loadShaderInclude("CM.glsl", includes); const auto VERTSRC = processShader("tex300.vert", includes); const auto FRAGBORDER1 = processShader("border.frag", includes); const auto QUADFRAGSRC = processShader("quad.frag", includes); const auto TEXFRAGSRCRGBA = processShader("rgba.frag", includes); GLuint prog = createProgram(VERTSRC, QUADFRAGSRC); m_rectShader.program = prog; m_rectShader.proj = glGetUniformLocation(prog, "proj"); m_rectShader.color = glGetUniformLocation(prog, "color"); m_rectShader.posAttrib = glGetAttribLocation(prog, "pos"); m_rectShader.topLeft = glGetUniformLocation(prog, "topLeft"); m_rectShader.fullSize = glGetUniformLocation(prog, "fullSize"); m_rectShader.radius = glGetUniformLocation(prog, "radius"); m_rectShader.roundingPower = glGetUniformLocation(prog, "roundingPower"); prog = createProgram(VERTSRC, TEXFRAGSRCRGBA); m_texShader.program = prog; m_texShader.proj = glGetUniformLocation(prog, "proj"); m_texShader.tex = glGetUniformLocation(prog, "tex"); m_texShader.alphaMatte = glGetUniformLocation(prog, "texMatte"); m_texShader.alpha = glGetUniformLocation(prog, "alpha"); m_texShader.texAttrib = glGetAttribLocation(prog, "texcoord"); m_texShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); m_texShader.posAttrib = glGetAttribLocation(prog, "pos"); m_texShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); m_texShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); m_texShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); m_texShader.topLeft = glGetUniformLocation(prog, "topLeft"); m_texShader.fullSize = glGetUniformLocation(prog, "fullSize"); m_texShader.radius = glGetUniformLocation(prog, "radius"); m_texShader.applyTint = glGetUniformLocation(prog, "applyTint"); m_texShader.tint = glGetUniformLocation(prog, "tint"); m_texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); m_texShader.roundingPower = glGetUniformLocation(prog, "roundingPower"); prog = createProgram(VERTSRC, FRAGBORDER1); m_borderShader.program = prog; m_borderShader.proj = glGetUniformLocation(prog, "proj"); m_borderShader.thick = glGetUniformLocation(prog, "thick"); m_borderShader.posAttrib = glGetAttribLocation(prog, "pos"); m_borderShader.texAttrib = glGetAttribLocation(prog, "texcoord"); m_borderShader.topLeft = glGetUniformLocation(prog, "topLeft"); m_borderShader.bottomRight = glGetUniformLocation(prog, "bottomRight"); m_borderShader.fullSize = glGetUniformLocation(prog, "fullSize"); m_borderShader.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed"); m_borderShader.radius = glGetUniformLocation(prog, "radius"); m_borderShader.radiusOuter = glGetUniformLocation(prog, "radiusOuter"); m_borderShader.gradient = glGetUniformLocation(prog, "gradient"); m_borderShader.gradientLength = glGetUniformLocation(prog, "gradientLength"); m_borderShader.angle = glGetUniformLocation(prog, "angle"); m_borderShader.gradient2 = glGetUniformLocation(prog, "gradient2"); m_borderShader.gradient2Length = glGetUniformLocation(prog, "gradient2Length"); m_borderShader.angle2 = glGetUniformLocation(prog, "angle2"); m_borderShader.gradientLerp = glGetUniformLocation(prog, "gradientLerp"); m_borderShader.alpha = glGetUniformLocation(prog, "alpha"); m_borderShader.roundingPower = glGetUniformLocation(prog, "roundingPower"); m_polyRenderFb = makeShared(); RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); } COpenGLRenderer::~COpenGLRenderer() { if (m_eglDisplay && m_eglContext != EGL_NO_CONTEXT) eglDestroyContext(m_eglDisplay, m_eglContext); if (m_eglDisplay) eglTerminate(m_eglDisplay); eglReleaseThread(); if (m_gbmDevice) gbm_device_destroy(m_gbmDevice); } bool COpenGLRenderer::explicitSyncSupported() { return !Env::envEnabled("HT_NO_EXPLICIT_SYNC") && m_syncobjSupported && m_exts.EGL_ANDROID_native_fence_sync_ext; } CBox COpenGLRenderer::logicalToGL(const CBox& box, bool transform) { auto b = box.copy(); b.scale(m_scale).round(); if (transform) b.transform(Hyprutils::Math::HYPRUTILS_TRANSFORM_FLIPPED_180, m_currentViewport.x, m_currentViewport.y); return b; } SP COpenGLRenderer::getRBO(SP buf) { for (const auto& r : m_rbos) { if (r->m_hlBuffer == buf) return r; } auto rbo = m_rbos.emplace_back(makeShared(buf, buf->dmabuf().format)); RASSERT(rbo->good(), "GL: Couldn't make a rbo for a render"); return rbo; } void COpenGLRenderer::onRenderbufferDestroy(CRenderbuffer* p) { std::erase_if(m_rbos, [p](const auto& rbo) { return !rbo || rbo.get() == p; }); } void COpenGLRenderer::waitOnSync() { auto timeline = exportSync(m_currentRBO->m_hlBuffer.lock()); if (!timeline) return; timeline->check(timeline->m_releasePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT); } void COpenGLRenderer::beginRendering(SP window, SP buf) { RASSERT(buf, "GL: null buffer passed to rendering"); makeEGLCurrent(); m_currentRBO = getRBO(buf); m_currentRBO->bind(); m_projection = Mat3x3::outputProjection(window->pixelSize(), HYPRUTILS_TRANSFORM_FLIPPED_180); m_currentViewport = window->pixelSize(); m_scale = window->scale(); m_window = window; m_damage = window->m_damageRing.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN); } void COpenGLRenderer::render(bool ignoreSync) { if (m_damage.empty()) return; if (!ignoreSync && explicitSyncSupported()) waitOnSync(); glViewport(0, 0, m_window->pixelSize().x, m_window->pixelSize().y); m_damage.forEachRect([this](const auto& RECT) { scissor(&RECT); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); }); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); m_alreadyRendered.clear(); renderBreadthfirst(m_window->m_rootElement); m_alreadyRendered.clear(); glDisable(GL_BLEND); } void COpenGLRenderer::renderBreadthfirst(SP e) { static const auto DEBUG_LAYOUT = Env::envEnabled("HT_DEBUG_LAYOUT"); CHyprColor DEBUG_COLOR = {Hyprgraphics::CColor::SHSL{.h = 0.0F, .s = 0.7F, .l = 0.5F}, 0.8F}; e->impl->breadthfirst([this, &DEBUG_COLOR](SP el) { if (el->impl->failedPositioning) return; if (std::ranges::find(m_alreadyRendered, el) != m_alreadyRendered.end()) return; el->paint(); m_alreadyRendered.emplace_back(el); if (DEBUG_LAYOUT) { auto BOX = el->impl->position.copy(); if (BOX.w == 0) BOX.w = 1; if (BOX.h == 0) BOX.h = 1; renderBorder(SBorderRenderData{ .box = BOX, .color = DEBUG_COLOR, .thick = 1, }); auto hsl = DEBUG_COLOR.asHSL(); hsl.h += 0.05F; if (hsl.h > 1.F) hsl.h -= 1.F; DEBUG_COLOR = CHyprColor{hsl, 0.8F}; } if (el->impl->clipChildren) { // clip children: push a clip box and render all children now, then pop box m_clipBoxes.emplace_back(logicalToGL(el->impl->position, false)); renderBreadthfirst(el); m_clipBoxes.pop_back(); } if (el->impl->grouped) { // grouped: render all children as one renderBreadthfirst(el); } }); } void COpenGLRenderer::endRendering() { m_currentRBO->unbind(); m_currentRBO.reset(); // FIXME: explicit sync for nvidia!!!! glFlush(); m_window->m_damageRing.rotate(); m_window.reset(); m_damage.clear(); } void COpenGLRenderer::scissor(const pixman_box32_t* box) { if (!box) { scissor(CBox{}); return; } scissor(CBox{ sc(box->x1), sc(box->y1), sc(box->x2 - box->x1), sc(box->y2 - box->y1), }); } void COpenGLRenderer::scissor(const CBox& box) { // only call glScissor if the box has changed static CBox m_lastScissorBox = {}; if (box.empty()) { glDisable(GL_SCISSOR_TEST); return; } if (box != m_lastScissorBox) { glScissor(box.x, box.y, box.width, box.height); m_lastScissorBox = box; } glEnable(GL_SCISSOR_TEST); } CRegion COpenGLRenderer::damageWithClip() { auto dmg = m_damage.copy(); for (const auto& cb : m_clipBoxes) { dmg.intersect(cb); } return dmg; } void COpenGLRenderer::renderRectangle(const SRectangleRenderData& data) { const auto ROUNDEDBOX = logicalToGL(data.box); const auto UNTRANSFORMED = logicalToGL(data.box, false); Mat3x3 matrix = m_projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_FLIPPED_180, data.box.rot); Mat3x3 glMatrix = m_projection.copy().multiply(matrix); const auto DAMAGE = damageWithClip(); if (DAMAGE.copy().intersect(UNTRANSFORMED).empty()) return; glUseProgram(m_rectShader.program); glUniformMatrix3fv(m_rectShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); // premultiply the color as well as we don't work with straight alpha const auto COL = data.color; glUniform4f(m_rectShader.color, COL.r * COL.a, COL.g * COL.a, COL.b * COL.a, COL.a); const auto TOPLEFT = Vector2D(UNTRANSFORMED.x, UNTRANSFORMED.y); const auto FULLSIZE = Vector2D(UNTRANSFORMED.width, UNTRANSFORMED.height); // Rounded corners glUniform2f(m_rectShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y); glUniform2f(m_rectShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); glUniform1f(m_rectShader.radius, data.rounding * m_scale); glUniform1f(m_rectShader.roundingPower, 2); glVertexAttribPointer(m_rectShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(m_rectShader.posAttrib); DAMAGE.forEachRect([this](const auto& RECT) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); glDisableVertexAttribArray(m_rectShader.posAttrib); } SP COpenGLRenderer::uploadTexture(const STextureData& data) { const auto TEX = makeShared(data.resource); TEX->m_fitMode = data.fitMode; return TEX; } static CBox containImage(const CBox& requested, const Vector2D& imageSize) { const auto SOURCE_ASPECT_RATIO = requested.w / requested.h; const auto IMAGE_ASPECT_RATIO = imageSize.x / imageSize.y; if (SOURCE_ASPECT_RATIO > IMAGE_ASPECT_RATIO) { // box is wider than the target const auto HEIGHT = requested.h; const auto WIDTH = (requested.h / imageSize.y) * imageSize.x; return CBox{ requested.x + ((requested.w - WIDTH) / 2.F), // requested.y, // WIDTH, // HEIGHT, // }; } // box is taller than the target const auto WIDTH = requested.w; const auto HEIGHT = (requested.w / imageSize.x) * imageSize.y; return CBox{ requested.x, // requested.y + ((requested.h - HEIGHT) / 2.F), // WIDTH, // HEIGHT, // }; } static std::array coverImage(const CBox& requested, const Vector2D& imageSize) { const float SOURCE_ASPECT_RATIO = requested.w / requested.h; const float IMAGE_ASPECT_RATIO = imageSize.x / imageSize.y; CBox texbox{}; if (SOURCE_ASPECT_RATIO > IMAGE_ASPECT_RATIO) { // box is wider than the target const float WIDTH = requested.w; const float HEIGHT = (requested.w / imageSize.x) * imageSize.y; texbox = { requested.x, requested.y - ((HEIGHT - requested.h) / 2.F), WIDTH, HEIGHT, }; } else { // box is taller than the target const float HEIGHT = requested.h; const float WIDTH = (requested.h / imageSize.y) * imageSize.x; texbox = { requested.x - ((WIDTH - requested.w) / 2.F), requested.y, WIDTH, HEIGHT, }; } // where does requested sit inside the enlarged texbox, in [0,1]? const float TOP = std::abs(requested.y - texbox.y) / texbox.h; const float LEFT = std::abs(requested.x - texbox.x) / texbox.w; const float BOTTOM = TOP + (requested.h / texbox.h); const float RIGHT = LEFT + (requested.w / texbox.w); std::array verts = { sc(RIGHT), sc(TOP), // top right sc(LEFT), sc(TOP), // top left sc(RIGHT), sc(BOTTOM), // bottom right sc(LEFT), sc(BOTTOM), // bottom left }; return verts; } static std::array tileImage(const CBox& requested, const Vector2D& imageSize) { const auto IMAGE_AS_PERCENT = imageSize / requested.size(); const auto INVERSE_RATIOS = Vector2D{1.F, 1.F} / IMAGE_AS_PERCENT; std::array verts = { sc(INVERSE_RATIOS.x), sc(0), // top right sc(0), sc(0), // top left sc(INVERSE_RATIOS.x), sc(INVERSE_RATIOS.y), // bottom right sc(0), sc(INVERSE_RATIOS.y), // bottom left }; return verts; } void COpenGLRenderer::renderTexture(const STextureRenderData& data) { RASSERT(data.texture->type() == IRendererTexture::TEXTURE_GL, "OpenGL renderer: passed a non-gl texture"); SP tex = reinterpretPointerCast(data.texture); const auto SOURCE_BOX = data.texture->fitMode() == IMAGE_FIT_MODE_CONTAIN ? containImage(data.box, tex->m_size) : data.box; const auto ROUNDEDBOX = logicalToGL(SOURCE_BOX); const auto UNTRANSFORMED = logicalToGL(SOURCE_BOX, false); Mat3x3 matrix = m_projMatrix.projectBox(ROUNDEDBOX, Hyprutils::Math::HYPRUTILS_TRANSFORM_FLIPPED_180, data.box.rot); Mat3x3 glMatrix = m_projection.copy().multiply(matrix); const auto DAMAGE = damageWithClip(); if (DAMAGE.copy().intersect(UNTRANSFORMED).empty()) return; CShader* shader = &m_texShader; glActiveTexture(GL_TEXTURE0); glBindTexture(tex->m_target, tex->m_texID); glUseProgram(shader->program); glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform1i(shader->tex, 0); glUniform1f(shader->alpha, data.a); const auto TOPLEFT = Vector2D(UNTRANSFORMED.x, UNTRANSFORMED.y); const auto FULLSIZE = Vector2D(UNTRANSFORMED.width, UNTRANSFORMED.height); // Rounded corners glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); glUniform1f(shader->radius, data.rounding * m_scale); glUniform1f(shader->roundingPower, 2); glUniform1i(shader->discardOpaque, 0); glUniform1i(shader->discardAlpha, 0); glUniform1i(shader->applyTint, 0); if (data.texture->fitMode() == IMAGE_FIT_MODE_STRETCH || data.texture->fitMode() == IMAGE_FIT_MODE_CONTAIN) { glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); } else if (data.texture->fitMode() == IMAGE_FIT_MODE_COVER) { const auto VERTS = coverImage(data.box, tex->m_size); glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, VERTS.data()); } else if (data.texture->fitMode() == IMAGE_FIT_MODE_TILE) { const auto VERTS = tileImage(data.box, tex->m_size); glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, VERTS.data()); glTexParameteri(tex->m_target, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(tex->m_target, GL_TEXTURE_WRAP_T, GL_REPEAT); } glEnableVertexAttribArray(shader->posAttrib); glEnableVertexAttribArray(shader->texAttrib); DAMAGE.forEachRect([this](const auto& RECT) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); glDisableVertexAttribArray(shader->posAttrib); glDisableVertexAttribArray(shader->texAttrib); glBindTexture(tex->m_target, 0); } void COpenGLRenderer::renderBorder(const SBorderRenderData& data) { const auto ROUNDEDBOX = logicalToGL(data.box); const auto UNTRANSFORMED = logicalToGL(data.box, false); Mat3x3 matrix = m_projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_FLIPPED_180, data.box.rot); Mat3x3 glMatrix = m_projection.copy().multiply(matrix); const auto DAMAGE = damageWithClip(); if (DAMAGE.copy().intersect(UNTRANSFORMED).empty()) return; glUseProgram(m_borderShader.program); glUniformMatrix3fv(m_borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); const auto OKLAB = data.color.asOkLab(); std::array grad = {sc(OKLAB.l), sc(OKLAB.a), sc(OKLAB.b), sc(data.color.a)}; glUniform4fv(m_borderShader.gradient, grad.size() / 4, (float*)grad.data()); glUniform1i(m_borderShader.gradientLength, grad.size() / 4); glUniform1f(m_borderShader.angle, (int)(0.F / (M_PI / 180.0)) % 360 * (M_PI / 180.0)); glUniform1f(m_borderShader.alpha, 1.F); glUniform1i(m_borderShader.gradient2Length, 0); const auto TOPLEFT = Vector2D(UNTRANSFORMED.x, UNTRANSFORMED.y); const auto FULLSIZE = Vector2D(UNTRANSFORMED.width, UNTRANSFORMED.height); glUniform2f(m_borderShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y); glUniform2f(m_borderShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); glUniform2f(m_borderShader.fullSizeUntransformed, (float)UNTRANSFORMED.width, (float)UNTRANSFORMED.height); glUniform1f(m_borderShader.radius, data.rounding * m_scale); glUniform1f(m_borderShader.radiusOuter, data.rounding * m_scale); glUniform1f(m_borderShader.roundingPower, 2); glUniform1f(m_borderShader.thick, data.thick * m_scale); glVertexAttribPointer(m_borderShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(m_borderShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(m_borderShader.posAttrib); glEnableVertexAttribArray(m_borderShader.texAttrib); DAMAGE.forEachRect([this](const auto& RECT) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); glDisableVertexAttribArray(m_borderShader.posAttrib); glDisableVertexAttribArray(m_borderShader.texAttrib); } void COpenGLRenderer::renderPolygon(const SPolygonRenderData& data) { const auto ROUNDEDBOX = logicalToGL(data.box); const auto UNTRANSFORMED = logicalToGL(data.box, false); const auto DAMAGE = damageWithClip(); if (DAMAGE.copy().intersect(UNTRANSFORMED).empty()) return; // We always do 4X MSAA on polygons, otherwise pixel galore Vector2D FB_SIZE = ROUNDEDBOX.size() * 2.F; Mat3x3 matrix = m_projMatrix.projectBox(CBox{{}, FB_SIZE}, HYPRUTILS_TRANSFORM_NORMAL, 0); auto proj = Mat3x3::outputProjection(FB_SIZE, HYPRUTILS_TRANSFORM_NORMAL); Mat3x3 glMatrix = proj.copy().multiply(matrix); m_polyRenderFb->alloc(FB_SIZE.x, FB_SIZE.y); m_polyRenderFb->bind(); glViewport(0, 0, FB_SIZE.x, FB_SIZE.y); scissor(nullptr); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(m_rectShader.program); glUniformMatrix3fv(m_rectShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); // premultiply the color as well as we don't work with straight alpha const auto COL = data.color; glUniform4f(m_rectShader.color, COL.r * COL.a, COL.g * COL.a, COL.b * COL.a, COL.a); glUniform1f(m_rectShader.radius, 0); std::vector verts; verts.resize(data.poly.m_points.size() * 2); for (size_t i = 0; i < data.poly.m_points.size(); i++) { verts[i * 2] = data.poly.m_points[i].x; verts[1 + (i * 2)] = data.poly.m_points[i].y; } glVertexAttribPointer(m_rectShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, verts.data()); glEnableVertexAttribArray(m_rectShader.posAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, verts.size() / 2); glDisableVertexAttribArray(m_rectShader.posAttrib); // bind back to our fbo and render auto tex = m_polyRenderFb->getTexture(); m_currentRBO->bind(); glViewport(0, 0, m_currentViewport.x, m_currentViewport.y); renderTexture(STextureRenderData{ .box = data.box, .texture = tex, }); } void COpenGLRenderer::renderLine(const SLineRenderData& data) { const auto ROUNDEDBOX = logicalToGL(data.box); const auto UNTRANSFORMED = logicalToGL(data.box, false); const auto DAMAGE = damageWithClip(); if (DAMAGE.copy().intersect(UNTRANSFORMED).empty()) return; if (data.points.size() <= 1) return; // generate a polygon // FIXME: inconsistent size on x/y, why? std::vector polyPoints; polyPoints.resize((data.points.size() * 4) - 2); for (size_t i = 0; i < data.points.size(); ++i) { Vector2D dir; if (i == data.points.size() - 1) { // last point: copy dir from last dir = (data.points.at(i) - data.points.at(i - 1)); } else dir = (data.points.at(i + 1) - data.points.at(i)); // normalize vec dir = dir / dir.size(); // rotate by 90 deg left and right const auto V1 = Vector2D{-dir.y, dir.x} * data.thick / ROUNDEDBOX.size(); const auto V2 = Vector2D{dir.y, -dir.x} * data.thick / ROUNDEDBOX.size(); polyPoints.at(i * 4) = data.points.at(i) + V1; polyPoints.at(1 + (i * 4)) = data.points.at(i) + V2; // then add the same for the next point, if there is one if (i + 1 < data.points.size()) { polyPoints.at(2 + (i * 4)) = data.points.at(i + 1) + V1; polyPoints.at(3 + (i * 4)) = data.points.at(i + 1) + V2; } } renderPolygon(SPolygonRenderData{ .box = data.box, .color = data.color, .poly = CPolygon{polyPoints}, }); } SP COpenGLRenderer::exportSync(SP buf) { if (!buf) return nullptr; auto rbo = getRBO(buf); if (!rbo) return nullptr; if (!rbo->m_syncTimeline) rbo->m_syncTimeline = CSyncTimeline::create(m_drmFD); return rbo->m_syncTimeline; } void COpenGLRenderer::signalRenderPoint(SP timeline) { auto sync = CEGLSync::create(); if (sync && sync->isValid()) { g_backend->doOnReadable(sync->takeFd(), [ap = timeline->m_acquirePoint, tl = WP{timeline}]() { if (!tl) return; tl->signal(ap); }); } } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/OpenGL.hpp000066400000000000000000000136221513450241200225440ustar00rootroot00000000000000#pragma once #include #include "../Renderer.hpp" #include "Shader.hpp" #include #include #include #include #include #include #include namespace Hyprtoolkit { class IToolkitWindow; class IElement; class CGLTexture; class CRenderbuffer; class CFramebuffer; class CEGLSync; class COpenGLRenderer : public IRenderer { public: COpenGLRenderer(int drmFD); virtual ~COpenGLRenderer(); virtual void beginRendering(SP window, SP buf); virtual void render(bool ignoreSync); virtual void endRendering(); virtual void renderRectangle(const SRectangleRenderData& data); virtual SP uploadTexture(const STextureData& data); virtual void renderTexture(const STextureRenderData& data); virtual void renderBorder(const SBorderRenderData& data); virtual void renderPolygon(const SPolygonRenderData& data); virtual void renderLine(const SLineRenderData& data); virtual SP exportSync(SP buf); virtual void signalRenderPoint(SP timeline); virtual bool explicitSyncSupported(); private: CBox logicalToGL(const CBox& box, bool transform = true); CRegion damageWithClip(); void scissor(const CBox& box); void scissor(const pixman_box32_t* box); void renderBreadthfirst(SP el); void waitOnSync(); void initEGL(bool gbm); EGLDeviceEXT eglDeviceFromDRMFD(int drmFD); EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); void makeEGLCurrent(); void unsetEGL(); SP getRBO(SP buf); void onRenderbufferDestroy(CRenderbuffer* p); Hyprutils::OS::CFileDescriptor m_gbmFD; gbm_device* m_gbmDevice = nullptr; EGLContext m_eglContext = nullptr; EGLDisplay m_eglDisplay = nullptr; EGLDeviceEXT m_eglDevice = nullptr; bool m_hasModifiers = true; int m_drmFD = -1; bool m_syncobjSupported = false; struct { PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT = nullptr; PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr; PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR = nullptr; PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = nullptr; PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr; PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr; PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr; } m_proc; struct { bool EXT_read_format_bgra = false; bool EXT_image_dma_buf_import = false; bool EXT_image_dma_buf_import_modifiers = false; bool KHR_display_reference = false; bool IMG_context_priority = false; bool EXT_create_context_robustness = false; bool EGL_ANDROID_native_fence_sync_ext = false; } m_exts; SP m_window; CRegion m_damage; float m_scale = 1.F; SP m_polyRenderFb; std::vector> m_rbos; SP m_currentRBO; std::vector m_clipBoxes; std::vector> m_alreadyRendered; CShader m_rectShader; CShader m_texShader; CShader m_borderShader; Mat3x3 m_projMatrix = Mat3x3::identity(); Mat3x3 m_projection; Vector2D m_currentViewport; friend class CRenderbuffer; friend class CGLTexture; friend class CEGLSync; }; inline SP g_openGL; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Renderbuffer.cpp000066400000000000000000000036751513450241200240330ustar00rootroot00000000000000#include "Renderbuffer.hpp" #include "OpenGL.hpp" #include "../../core/InternalBackend.hpp" #include #include #include using namespace Hyprtoolkit; CRenderbuffer::~CRenderbuffer() { g_openGL->makeEGLCurrent(); unbind(); m_framebuffer.release(); glDeleteRenderbuffers(1, &m_rbo); g_openGL->m_proc.eglDestroyImageKHR(g_openGL->m_eglDisplay, m_image); } CRenderbuffer::CRenderbuffer(SP buffer, uint32_t format) : m_hlBuffer(buffer), m_drmFormat(format) { auto dma = buffer->dmabuf(); m_image = g_openGL->createEGLImage(dma); if (m_image == EGL_NO_IMAGE_KHR) { g_logger->log(HT_LOG_ERROR, "gl: createEGLImage failed for rbo"); return; } glGenRenderbuffers(1, &m_rbo); glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); g_openGL->m_proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, m_image); glBindRenderbuffer(GL_RENDERBUFFER, 0); glGenFramebuffers(1, &m_framebuffer.m_fb); m_framebuffer.m_fbAllocated = true; m_framebuffer.m_size = buffer->size; m_framebuffer.bind(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { g_logger->log(HT_LOG_ERROR, "rbo: glCheckFramebufferStatus failed"); return; } m_framebuffer.unbind(); m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_openGL->onRenderbufferDestroy(this); }); m_good = true; } bool CRenderbuffer::good() { return m_good; } void CRenderbuffer::bind() { glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); bindFB(); } void CRenderbuffer::bindFB() { m_framebuffer.bind(); } void CRenderbuffer::unbind() { glBindRenderbuffer(GL_RENDERBUFFER, 0); m_framebuffer.unbind(); } CFramebuffer* CRenderbuffer::getFB() { return &m_framebuffer; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Renderbuffer.hpp000066400000000000000000000020131513450241200240210ustar00rootroot00000000000000#pragma once #include "../../helpers/Memory.hpp" #include "Framebuffer.hpp" #include #include namespace Hyprtoolkit { class CSyncTimeline; class CEGLSync; class CRenderbuffer { public: CRenderbuffer(SP buffer, uint32_t format); ~CRenderbuffer(); bool good(); void bind(); void bindFB(); void unbind(); CFramebuffer* getFB(); uint32_t getFormat(); WP m_hlBuffer; SP m_syncTimeline; private: void* m_image = nullptr; GLuint m_rbo = 0; CFramebuffer m_framebuffer; uint32_t m_drmFormat = 0; bool m_good = false; struct { Hyprutils::Signal::CHyprSignalListener destroyBuffer; } m_listeners; }; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Shader.cpp000066400000000000000000000007131513450241200226160ustar00rootroot00000000000000#include "Shader.hpp" GLint CShader::getUniformLocation(const std::string& unif) { const auto itpos = m_muUniforms.find(unif); if (itpos == m_muUniforms.end()) { const auto unifLoc = glGetUniformLocation(program, unif.c_str()); m_muUniforms[unif] = unifLoc; return unifLoc; } return itpos->second; } CShader::~CShader() { destroy(); } void CShader::destroy() { glDeleteProgram(program); program = 0; }hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Shader.hpp000066400000000000000000000036551513450241200226330ustar00rootroot00000000000000#pragma once #include #include #include class CShader { public: ~CShader(); GLuint program = 0; GLint proj = -1; GLint color = -1; GLint alphaMatte = -1; GLint tex = -1; GLint tex2 = -1; GLint alpha = -1; GLfloat mixFactor = -1; GLint posAttrib = -1; GLint texAttrib = -1; GLint matteTexAttrib = -1; GLint discardOpaque = -1; GLint discardAlpha = -1; GLfloat discardAlphaValue = -1; GLint topLeft = -1; GLint bottomRight = -1; GLint fullSize = -1; GLint fullSizeUntransformed = -1; GLint radius = -1; GLint radiusOuter = -1; GLint roundingPower = -1; GLint thick = -1; GLint halfpixel = -1; GLint range = -1; GLint shadowPower = -1; GLint useAlphaMatte = -1; // always inverted GLint applyTint = -1; GLint tint = -1; GLint gradient = -1; GLint gradientLength = -1; GLint gradient2 = -1; GLint gradient2Length = -1; GLint gradientLerp = -1; GLint angle = -1; GLint angle2 = -1; GLint time = -1; GLint distort = -1; GLint wl_output = -1; // Blur prepare GLint contrast = -1; // Blur GLint passes = -1; // Used by `vibrancy` GLint vibrancy = -1; GLint vibrancy_darkness = -1; // Blur finish GLint brightness = -1; GLint noise = -1; // colorize GLint colorize = -1; GLint colorizeTint = -1; GLint boostA = -1; GLint getUniformLocation(const std::string&); void destroy(); private: std::unordered_map m_muUniforms; };hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Sync.cpp000066400000000000000000000025171513450241200223300ustar00rootroot00000000000000#include "Sync.hpp" #include "../../core/InternalBackend.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::OS; UP CEGLSync::create() { EGLSyncKHR sync = g_openGL->m_proc.eglCreateSyncKHR(g_openGL->m_eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); if (sync == EGL_NO_SYNC_KHR) { g_logger->log(HT_LOG_ERROR, "eglCreateSyncKHR failed"); return nullptr; } // we need to flush otherwise we might not get a valid fd glFlush(); int fd = g_openGL->m_proc.eglDupNativeFenceFDANDROID(g_openGL->m_eglDisplay, sync); if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { g_logger->log(HT_LOG_ERROR, "eglDupNativeFenceFDANDROID failed"); return nullptr; } UP eglSync(new CEGLSync); eglSync->m_fd = CFileDescriptor(fd); eglSync->m_sync = sync; eglSync->m_valid = true; return eglSync; } CEGLSync::~CEGLSync() { if (m_sync == EGL_NO_SYNC_KHR) return; if (g_openGL && g_openGL->m_proc.eglDestroySyncKHR(g_openGL->m_eglDisplay, m_sync) != EGL_TRUE) g_logger->log(HT_LOG_ERROR, "eglDestroySyncKHR failed"); } CFileDescriptor& CEGLSync::fd() { return m_fd; } CFileDescriptor&& CEGLSync::takeFd() { return std::move(m_fd); } bool CEGLSync::isValid() { return m_valid && m_sync != EGL_NO_SYNC_KHR && m_fd.isValid(); } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/Sync.hpp000066400000000000000000000012101513450241200223220ustar00rootroot00000000000000#pragma once #include "OpenGL.hpp" #include "../../helpers/Memory.hpp" #include namespace Hyprtoolkit { class CEGLSync { public: static UP create(); ~CEGLSync(); Hyprutils::OS::CFileDescriptor& fd(); Hyprutils::OS::CFileDescriptor&& takeFd(); bool isValid(); private: CEGLSync() = default; Hyprutils::OS::CFileDescriptor m_fd; EGLSyncKHR m_sync = EGL_NO_SYNC_KHR; bool m_valid = false; friend class CHyprOpenGLImpl; }; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/000077500000000000000000000000001513450241200223345ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/000077500000000000000000000000001513450241200232755ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/CM.frag000066400000000000000000000026131513450241200244370ustar00rootroot00000000000000#version 300 es //#extension GL_OES_EGL_image_external : require #extension GL_ARB_shading_language_include : enable precision highp float; in vec2 v_texcoord; uniform sampler2D tex; //uniform samplerExternalOES texture0; uniform int texType; // eTextureType: 0 - rgba, 1 - rgbx, 2 - ext // uniform int skipCM; uniform int sourceTF; // eTransferFunction uniform int targetTF; // eTransferFunction uniform mat4x2 targetPrimaries; uniform float alpha; uniform int discardOpaque; uniform int discardAlpha; uniform float discardAlphaValue; uniform int applyTint; uniform vec3 tint; #include "rounding.glsl" #include "CM.glsl" layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor; if (texType == 1) pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); // else if (texType == 2) // pixColor = texture(texture0, v_texcoord); else // assume rgba pixColor = texture(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) discard; if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) discard; // this shader shouldn't be used when skipCM == 1 pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); if (applyTint == 1) pixColor = vec4(pixColor.rgb * tint.rgb, pixColor[3]); if (radius > 0.0) pixColor = rounding(pixColor); fragColor = pixColor * alpha; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/CM.glsl000066400000000000000000000332761513450241200244720ustar00rootroot00000000000000uniform vec2 srcTFRange; uniform vec2 dstTFRange; uniform float maxLuminance; uniform float dstMaxLuminance; uniform float dstRefLuminance; uniform float sdrSaturation; uniform float sdrBrightnessMultiplier; uniform mat3 convertMatrix; //enum eTransferFunction #define CM_TRANSFER_FUNCTION_BT1886 1 #define CM_TRANSFER_FUNCTION_GAMMA22 2 #define CM_TRANSFER_FUNCTION_GAMMA28 3 #define CM_TRANSFER_FUNCTION_ST240 4 #define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 #define CM_TRANSFER_FUNCTION_LOG_100 6 #define CM_TRANSFER_FUNCTION_LOG_316 7 #define CM_TRANSFER_FUNCTION_XVYCC 8 #define CM_TRANSFER_FUNCTION_SRGB 9 #define CM_TRANSFER_FUNCTION_EXT_SRGB 10 #define CM_TRANSFER_FUNCTION_ST2084_PQ 11 #define CM_TRANSFER_FUNCTION_ST428 12 #define CM_TRANSFER_FUNCTION_HLG 13 // sRGB constants #define SRGB_POW 2.4 #define SRGB_CUT 0.0031308 #define SRGB_SCALE 12.92 #define SRGB_ALPHA 1.055 #define BT1886_POW (1.0 / 0.45) #define BT1886_CUT 0.018053968510807 #define BT1886_SCALE 4.5 #define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) // See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf #define ST240_POW (1.0 / 0.45) #define ST240_CUT 0.0228 #define ST240_SCALE 4.0 #define ST240_ALPHA 1.1115 #define ST428_POW 2.6 #define ST428_SCALE (52.37 / 48.0) // PQ constants #define PQ_M1 0.1593017578125 #define PQ_M2 78.84375 #define PQ_INV_M1 (1.0 / PQ_M1) #define PQ_INV_M2 (1.0 / PQ_M2) #define PQ_C1 0.8359375 #define PQ_C2 18.8515625 #define PQ_C3 18.6875 // HLG constants #define HLG_D_CUT (1.0 / 12.0) #define HLG_E_CUT 0.5 #define HLG_A 0.17883277 #define HLG_B 0.28466892 #define HLG_C 0.55991073 #define SDR_MIN_LUMINANCE 0.2 #define SDR_MAX_LUMINANCE 80.0 #define HDR_MIN_LUMINANCE 0.005 #define HDR_MAX_LUMINANCE 10000.0 #define HLG_MAX_LUMINANCE 1000.0 #define M_E 2.718281828459045 vec3 xy2xyz(vec2 xy) { if (xy.y == 0.0) return vec3(0.0, 0.0, 0.0); return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y); } vec4 saturate(vec4 color, mat3 primaries, float saturation) { if (saturation == 1.0) return color; vec3 brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]); float Y = dot(color.rgb, brightness); return vec4(mix(vec3(Y), color.rgb, saturation), color[3]); } // The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf vec3 tfInvPQ(vec3 color) { vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); return pow( (max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), vec3(PQ_INV_M1) ); } vec3 tfInvHLG(vec3 color) { bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT)); vec3 lo = color.rgb * color.rgb / 3.0; vec3 hi = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0; return mix(hi, lo, isLow); } // Many transfer functions (including sRGB) follow the same pattern: a linear // segment for small values and a power function for larger values. The // following function implements this pattern from which sRGB, BT.1886, and // others can be derived by plugging in the right constants. vec3 tfInvLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { bvec3 isLow = lessThanEqual(color.rgb, vec3(thres * scale)); vec3 lo = color.rgb / scale; vec3 hi = pow((color.rgb + alpha - 1.0) / alpha, vec3(gamma)); return mix(hi, lo, isLow); } vec3 tfInvSRGB(vec3 color) { return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); } vec3 tfInvExtSRGB(vec3 color) { // EXT sRGB is the sRGB transfer function mirrored around 0. return sign(color) * tfInvSRGB(abs(color)); } vec3 tfInvBT1886(vec3 color) { return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); } vec3 tfInvXVYCC(vec3 color) { // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0, // same as what EXT sRGB is to sRGB. return sign(color) * tfInvBT1886(abs(color)); } vec3 tfInvST240(vec3 color) { return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); } // Forward transfer functions corresponding to the inverse functions above. vec3 tfPQ(vec3 color) { vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1)); return pow( (vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), vec3(PQ_M2) ); } vec3 tfHLG(vec3 color) { bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT)); vec3 lo = sqrt(max(color.rgb, vec3(0.0)) * 3.0); vec3 hi = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C; return mix(hi, lo, isLow); } vec3 tfLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { bvec3 isLow = lessThanEqual(color.rgb, vec3(thres)); vec3 lo = color.rgb * scale; vec3 hi = pow(color.rgb, vec3(1.0 / gamma)) * alpha - (alpha - 1.0); return mix(hi, lo, isLow); } vec3 tfSRGB(vec3 color) { return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); } vec3 tfExtSRGB(vec3 color) { // EXT sRGB is the sRGB transfer function mirrored around 0. return sign(color) * tfSRGB(abs(color)); } vec3 tfBT1886(vec3 color) { return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); } vec3 tfXVYCC(vec3 color) { // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0, // same as what EXT sRGB is to sRGB. return sign(color) * tfBT1886(abs(color)); } vec3 tfST240(vec3 color) { return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); } vec3 toLinearRGB(vec3 color, int tf) { switch (tf) { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(2.2)); case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(2.8)); case CM_TRANSFER_FUNCTION_HLG: return tfInvHLG(color); case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfInvExtSRGB(color); case CM_TRANSFER_FUNCTION_BT1886: return tfInvBT1886(color); case CM_TRANSFER_FUNCTION_ST240: return tfInvST240(color); case CM_TRANSFER_FUNCTION_LOG_100: return mix(exp((color - 1.0) * 2.0 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); case CM_TRANSFER_FUNCTION_LOG_316: return mix(exp((color - 1.0) * 2.5 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); case CM_TRANSFER_FUNCTION_XVYCC: return tfInvXVYCC(color); case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)), vec3(ST428_POW)) * ST428_SCALE; case CM_TRANSFER_FUNCTION_SRGB: default: return tfInvSRGB(color); } } vec4 toLinear(vec4 color, int tf) { if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) return color; color.rgb /= max(color.a, 0.001); color.rgb = toLinearRGB(color.rgb, tf); color.rgb *= color.a; return color; } vec4 toNit(vec4 color, vec2 range) { color.rgb = color.rgb * (range[1] - range[0]) + range[0]; return color; } vec3 fromLinearRGB(vec3 color, int tf) { switch (tf) { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfPQ(color); case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.2)); case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.8)); case CM_TRANSFER_FUNCTION_HLG: return tfHLG(color); case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfExtSRGB(color); case CM_TRANSFER_FUNCTION_BT1886: return tfBT1886(color); case CM_TRANSFER_FUNCTION_ST240: return tfST240(color); case CM_TRANSFER_FUNCTION_LOG_100: return mix(1.0 + log(color) / log(10.0) / 2.0, vec3(0.0), lessThanEqual(color, vec3(0.01))); case CM_TRANSFER_FUNCTION_LOG_316: return mix(1.0 + log(color) / log(10.0) / 2.5, vec3(0.0), lessThanEqual(color, vec3(sqrt(10.0) / 1000.0))); case CM_TRANSFER_FUNCTION_XVYCC: return tfXVYCC(color); case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)) / ST428_SCALE, vec3(1.0 / ST428_POW)); case CM_TRANSFER_FUNCTION_SRGB: default: return tfSRGB(color); } } vec4 fromLinear(vec4 color, int tf) { if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) return color; color.rgb /= max(color.a, 0.001); color.rgb = fromLinearRGB(color.rgb, tf); color.rgb *= color.a; return color; } vec4 fromLinearNit(vec4 color, int tf, vec2 range) { if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) color.rgb = color.rgb / SDR_MAX_LUMINANCE; else { color.rgb /= max(color.a, 0.001); color.rgb = (color.rgb - range[0]) / (range[1] - range[0]); color.rgb = fromLinearRGB(color.rgb, tf); color.rgb *= color.a; } return color; } mat3 primaries2xyz(mat4x2 primaries) { vec3 r = xy2xyz(primaries[0]); vec3 g = xy2xyz(primaries[1]); vec3 b = xy2xyz(primaries[2]); vec3 w = xy2xyz(primaries[3]); mat3 invMat = inverse( mat3( r.x, r.y, r.z, g.x, g.y, g.z, b.x, b.y, b.z ) ); vec3 s = invMat * w; return mat3( s.r * r.x, s.r * r.y, s.r * r.z, s.g * g.x, s.g * g.y, s.g * g.z, s.b * b.x, s.b * b.y, s.b * b.z ); } mat3 adaptWhite(vec2 src, vec2 dst) { if (src == dst) return mat3( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ); // const vec2 D65 = vec2(0.3127, 0.3290); const mat3 Bradford = mat3( 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296 ); mat3 BradfordInv = inverse(Bradford); vec3 srcXYZ = xy2xyz(src); vec3 dstXYZ = xy2xyz(dst); vec3 factors = (Bradford * dstXYZ) / (Bradford * srcXYZ); return BradfordInv * mat3( factors.x, 0.0, 0.0, 0.0, factors.y, 0.0, 0.0, 0.0, factors.z ) * Bradford; } vec4 convertPrimaries(vec4 color, mat3 src, vec2 srcWhite, mat3 dst, vec2 dstWhite) { mat3 convMat = inverse(dst) * adaptWhite(srcWhite, dstWhite) * src; return vec4(convMat * color.rgb, color[3]); } const mat3 BT2020toLMS = mat3( 0.3592, 0.6976, -0.0358, -0.1922, 1.1004, 0.0755, 0.0070, 0.0749, 0.8434 ); //const mat3 LMStoBT2020 = inverse(BT2020toLMS); const mat3 LMStoBT2020 = mat3( 2.0701800566956135096, -1.3264568761030210255, 0.20661600684785517081, 0.36498825003265747974, 0.68046736285223514102, -0.045421753075853231409, -0.049595542238932107896, -0.049421161186757487412, 1.1879959417328034394 ); // const mat3 ICtCpPQ = transpose(mat3( // 2048.0, 2048.0, 0.0, // 6610.0, -13613.0, 7003.0, // 17933.0, -17390.0, -543.0 // ) / 4096.0); const mat3 ICtCpPQ = mat3( 0.5, 1.61376953125, 4.378173828125, 0.5, -3.323486328125, -4.24560546875, 0.0, 1.709716796875, -0.132568359375 ); //const mat3 ICtCpPQInv = inverse(ICtCpPQ); const mat3 ICtCpPQInv = mat3( 1.0, 1.0, 1.0, 0.0086090370379327566, -0.0086090370379327566, 0.560031335710679118, 0.11102962500302595656, -0.11102962500302595656, -0.32062717498731885185 ); // unused for now // const mat3 ICtCpHLG = transpose(mat3( // 2048.0, 2048.0, 0.0, // 3625.0, -7465.0, 3840.0, // 9500.0, -9212.0, -288.0 // ) / 4096.0); // const mat3 ICtCpHLGInv = inverse(ICtCpHLG); vec4 tonemap(vec4 color, mat3 dstXYZ) { if (maxLuminance < dstMaxLuminance * 1.01) return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]); mat3 toLMS = BT2020toLMS * dstXYZ; mat3 fromLMS = inverse(dstXYZ) * LMStoBT2020; vec3 lms = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; vec3 ICtCp = ICtCpPQ * lms; float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); float luminance = pow( (max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1 ) * HDR_MAX_LUMINANCE; float srcScale = maxLuminance / dstRefLuminance; float dstScale = dstMaxLuminance / dstRefLuminance; float minScale = min(srcScale, 1.5); float dimming = 1.0 / clamp(minScale / dstScale, 1.0, minScale); float refLuminance = dstRefLuminance * dimming; float low = min(luminance * dimming, refLuminance); float highlight = clamp((luminance / dstRefLuminance - 1.0) / (srcScale - 1.0), 0.0, 1.0); float high = log(highlight * (M_E - 1.0) + 1.0) * (dstMaxLuminance - refLuminance); luminance = low + high; E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_M1); ICtCp[0] = pow( (PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), PQ_M2 ) / HDR_MAX_LUMINANCE; return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, color[3]); } vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat4x2 dstPrimaries) { pixColor.rgb /= max(pixColor.a, 0.001); pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); pixColor.rgb = convertMatrix * pixColor.rgb; pixColor = toNit(pixColor, srcTFRange); pixColor.rgb *= pixColor.a; mat3 dstxyz = primaries2xyz(dstPrimaries); pixColor = tonemap(pixColor, dstxyz); pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); if (srcTF == CM_TRANSFER_FUNCTION_SRGB && dstTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { pixColor = saturate(pixColor, dstxyz, sdrSaturation); pixColor.rgb *= sdrBrightnessMultiplier; } return pixColor; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/border.frag000066400000000000000000000132511513450241200254150ustar00rootroot00000000000000#version 300 es #extension GL_ARB_shading_language_include : enable precision highp float; in vec2 v_texcoord; uniform int skipCM; uniform int sourceTF; // eTransferFunction uniform int targetTF; // eTransferFunction uniform mat4x2 targetPrimaries; uniform vec2 fullSizeUntransformed; uniform float radiusOuter; uniform float thick; // Gradients are in OkLabA!!!! {l, a, b, alpha} uniform vec4 gradient[10]; uniform vec4 gradient2[10]; uniform int gradientLength; uniform int gradient2Length; uniform float angle; uniform float angle2; uniform float gradientLerp; uniform float alpha; #include "rounding.glsl" #include "CM.glsl" vec4 okLabAToSrgb(vec4 lab) { float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); return vec4(fromLinearRGB( vec3( l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965), l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010 ), CM_TRANSFER_FUNCTION_SRGB ), lab[3]); } vec4 getOkColorForCoordArray1(vec2 normalizedCoord) { if (gradientLength < 2) return gradient[0]; float finalAng = 0.0; if (angle > 4.71 /* 270 deg */) { normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = 6.28 - angle; } else if (angle > 3.14 /* 180 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = angle - 3.14; } else if (angle > 1.57 /* 90 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; finalAng = 3.14 - angle; } else { finalAng = angle; } float sine = sin(finalAng); float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); int bottom = int(floor(progress)); int top = bottom + 1; return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); } vec4 getOkColorForCoordArray2(vec2 normalizedCoord) { if (gradient2Length < 2) return gradient2[0]; float finalAng = 0.0; if (angle2 > 4.71 /* 270 deg */) { normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = 6.28 - angle; } else if (angle2 > 3.14 /* 180 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = angle - 3.14; } else if (angle2 > 1.57 /* 90 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; finalAng = 3.14 - angle2; } else { finalAng = angle2; } float sine = sin(finalAng); float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1); int bottom = int(floor(progress)); int top = bottom + 1; return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress); } vec4 getColorForCoord(vec2 normalizedCoord) { vec4 result1 = getOkColorForCoordArray1(normalizedCoord); if (gradient2Length <= 0) return okLabAToSrgb(result1); vec4 result2 = getOkColorForCoordArray2(normalizedCoord); return okLabAToSrgb(mix(result1, result2, gradientLerp)); } layout(location = 0) out vec4 fragColor; void main() { highp vec2 pixCoord = vec2(gl_FragCoord); highp vec2 pixCoordOuter = pixCoord; highp vec2 originalPixCoord = v_texcoord; originalPixCoord *= fullSizeUntransformed; float additionalAlpha = 1.0; vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); bool done = false; pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; pixCoordOuter = pixCoord; pixCoord -= fullSize * 0.5 - radius; pixCoordOuter -= fullSize * 0.5 - radiusOuter; // center the pixes don't make it top-left pixCoord += vec2(1.0, 1.0) / fullSize; pixCoordOuter += vec2(1.0, 1.0) / fullSize; if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { float dist = pow(pow(pixCoord.x,roundingPower)+pow(pixCoord.y,roundingPower),1.0/roundingPower); float distOuter = pow(pow(pixCoordOuter.x,roundingPower)+pow(pixCoordOuter.y,roundingPower),1.0/roundingPower); float h = (thick / 2.0); if (dist < radius - h) { // lower float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); additionalAlpha *= normalized; done = true; } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { // higher float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); additionalAlpha *= normalized; done = true; } else if (distOuter < radiusOuter - h) { additionalAlpha = 1.0; done = true; } } // now check for other shit if (!done) { // distance to all straight bb borders float distanceT = originalPixCoord[1]; float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; float distanceL = originalPixCoord[0]; float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; // get the smallest float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); if (smallest > thick) discard; } if (additionalAlpha == 0.0) discard; pixColor = getColorForCoord(v_texcoord); pixColor.rgb *= pixColor[3]; pixColor *= alpha * additionalAlpha; fragColor = pixColor; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/passthru.frag000066400000000000000000000003041513450241200260040ustar00rootroot00000000000000#version 300 es precision highp float; in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; layout(location = 0) out vec4 fragColor; void main() { fragColor = texture(tex, v_texcoord); } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/quad.frag000066400000000000000000000004701513450241200250710ustar00rootroot00000000000000#version 300 es #extension GL_ARB_shading_language_include : enable precision highp float; in vec4 v_color; #include "rounding.glsl" layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = v_color; if (radius > 0.0) pixColor = rounding(pixColor); fragColor = pixColor; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/rgba.frag000066400000000000000000000015051513450241200250520ustar00rootroot00000000000000#version 300 es #extension GL_ARB_shading_language_include : enable precision highp float; in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform float alpha; #include "rounding.glsl" uniform int discardOpaque; uniform int discardAlpha; uniform float discardAlphaValue; uniform int applyTint; uniform vec3 tint; layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = texture(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) discard; if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) discard; if (applyTint == 1) { pixColor[0] = pixColor[0] * tint[0]; pixColor[1] = pixColor[1] * tint[1]; pixColor[2] = pixColor[2] * tint[2]; } if (radius > 0.0) pixColor = rounding(pixColor); fragColor = pixColor * alpha; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/rounding.glsl000066400000000000000000000017071513450241200260120ustar00rootroot00000000000000// smoothing constant for the edge: more = blurrier, but smoother #define M_PI 3.1415926535897932384626433832795 #define SMOOTHING_CONSTANT (M_PI / 5.34665792551) uniform float radius; uniform float roundingPower; uniform vec2 topLeft; uniform vec2 fullSize; vec4 rounding(vec4 color) { vec2 pixCoord = vec2(gl_FragCoord); pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; pixCoord -= fullSize * 0.5 - radius; pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix don't make it top-left if (pixCoord.x + pixCoord.y > radius) { float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0/roundingPower); if (dist > radius + SMOOTHING_CONSTANT) discard; float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); color *= normalized; } return color; } hyprwm-hyprtoolkit-71515e8/src/renderer/gl/shaders/glsl/tex300.vert000066400000000000000000000005131513450241200252210ustar00rootroot00000000000000#version 300 es uniform mat3 proj; uniform vec4 color; in vec2 pos; in vec2 texcoord; in vec2 texcoordMatte; out vec4 v_color; out vec2 v_texcoord; out vec2 v_texcoordMatte; void main() { gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); v_color = color; v_texcoord = texcoord; v_texcoordMatte = texcoordMatte; } hyprwm-hyprtoolkit-71515e8/src/renderer/sync/000077500000000000000000000000001513450241200212555ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/renderer/sync/SyncTimeline.cpp000066400000000000000000000121411513450241200243630ustar00rootroot00000000000000#include "SyncTimeline.hpp" #include "../../Macros.hpp" #include "../../core/InternalBackend.hpp" #include "../Renderer.hpp" #include #include using namespace Hyprutils::OS; using namespace Hyprtoolkit; SP CSyncTimeline::create(int drmFD_) { RASSERT(g_renderer->explicitSyncSupported(), "Tried to create a sync timeline without ES support"); auto timeline = SP(new CSyncTimeline); timeline->m_drmFD = drmFD_; timeline->m_self = timeline; if (drmSyncobjCreate(drmFD_, 0, &timeline->m_handle)) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline: failed to create a drm syncobj??"); return nullptr; } int objFd = -1; if (drmSyncobjHandleToFD(drmFD_, timeline->m_handle, &objFd)) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline: failed to get a syncobj fd??"); return nullptr; } timeline->m_syncobjFD = Hyprutils::OS::CFileDescriptor(objFd); return timeline; } SP CSyncTimeline::create(int drmFD_, CFileDescriptor&& drmSyncobjFD) { RASSERT(g_renderer->explicitSyncSupported(), "Tried to create a sync timeline without ES support"); auto timeline = SP(new CSyncTimeline); timeline->m_drmFD = drmFD_; timeline->m_syncobjFD = std::move(drmSyncobjFD); timeline->m_self = timeline; if (drmSyncobjFDToHandle(drmFD_, timeline->m_syncobjFD.get(), &timeline->m_handle)) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline: failed to create a drm syncobj from fd??"); return nullptr; } return timeline; } CSyncTimeline::~CSyncTimeline() { if (m_handle == 0) return; drmSyncobjDestroy(m_drmFD, m_handle); } std::optional CSyncTimeline::check(uint64_t point, uint32_t flags) { #ifdef __FreeBSD__ constexpr int ETIME_ERR = ETIMEDOUT; #else constexpr int ETIME_ERR = ETIME; #endif uint32_t signaled = 0; int ret = drmSyncobjTimelineWait(m_drmFD, &m_handle, &point, 1, 0, flags, &signaled); if (ret != 0 && ret != -ETIME_ERR) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline::check: drmSyncobjTimelineWait failed"); return std::nullopt; } return ret == 0; } bool CSyncTimeline::addWaiter(std::function&& waiter, uint64_t point, uint32_t flags) { auto eventFd = CFileDescriptor(eventfd(0, EFD_CLOEXEC)); if (!eventFd.isValid()) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline::addWaiter: failed to acquire an eventfd"); return false; } if (drmSyncobjEventfd(m_drmFD, m_handle, point, eventFd.get(), flags)) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline::addWaiter: drmSyncobjEventfd failed"); return false; } g_backend->doOnReadable(std::move(eventFd), std::move(waiter)); return true; } CFileDescriptor CSyncTimeline::exportAsSyncFileFD(uint64_t src) { int sync = -1; uint32_t syncHandle = 0; if (drmSyncobjCreate(m_drmFD, 0, &syncHandle)) { g_logger->log(HT_LOG_ERROR, "exportAsSyncFileFD: drmSyncobjCreate failed"); return {}; } if (drmSyncobjTransfer(m_drmFD, syncHandle, 0, m_handle, src, 0)) { g_logger->log(HT_LOG_ERROR, "exportAsSyncFileFD: drmSyncobjTransfer failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return {}; } if (drmSyncobjExportSyncFile(m_drmFD, syncHandle, &sync)) { g_logger->log(HT_LOG_ERROR, "exportAsSyncFileFD: drmSyncobjExportSyncFile failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return {}; } drmSyncobjDestroy(m_drmFD, syncHandle); return CFileDescriptor{sync}; } bool CSyncTimeline::importFromSyncFileFD(uint64_t dst, CFileDescriptor& fd) { uint32_t syncHandle = 0; if (drmSyncobjCreate(m_drmFD, 0, &syncHandle)) { g_logger->log(HT_LOG_ERROR, "importFromSyncFileFD: drmSyncobjCreate failed"); return false; } if (drmSyncobjImportSyncFile(m_drmFD, syncHandle, fd.get())) { g_logger->log(HT_LOG_ERROR, "importFromSyncFileFD: drmSyncobjImportSyncFile failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return false; } if (drmSyncobjTransfer(m_drmFD, m_handle, dst, syncHandle, 0, 0)) { g_logger->log(HT_LOG_ERROR, "importFromSyncFileFD: drmSyncobjTransfer failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return false; } drmSyncobjDestroy(m_drmFD, syncHandle); return true; } bool CSyncTimeline::transfer(SP from, uint64_t fromPoint, uint64_t toPoint) { if (m_drmFD != from->m_drmFD) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline::transfer: cannot transfer timelines between gpus, {} -> {}", from->m_drmFD, m_drmFD); return false; } if (drmSyncobjTransfer(m_drmFD, m_handle, toPoint, from->m_handle, fromPoint, 0)) { g_logger->log(HT_LOG_ERROR, "CSyncTimeline::transfer: drmSyncobjTransfer failed"); return false; } return true; } void CSyncTimeline::signal(uint64_t point) { if (drmSyncobjTimelineSignal(m_drmFD, &m_handle, &point, 1)) g_logger->log(HT_LOG_ERROR, "CSyncTimeline::signal: drmSyncobjTimelineSignal failed"); } hyprwm-hyprtoolkit-71515e8/src/renderer/sync/SyncTimeline.hpp000066400000000000000000000027551513450241200244020ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../../helpers/Memory.hpp" /* Yoinked from Hyprland. */ namespace Hyprtoolkit { class CSyncTimeline { public: static SP create(int drmFD_); static SP create(int drmFD_, Hyprutils::OS::CFileDescriptor&& drmSyncobjFD); ~CSyncTimeline(); // check if the timeline point has been signaled // flags: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT or DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE // std::nullopt on fail std::optional check(uint64_t point, uint32_t flags); bool addWaiter(std::function&& waiter, uint64_t point, uint32_t flags); Hyprutils::OS::CFileDescriptor exportAsSyncFileFD(uint64_t src); bool importFromSyncFileFD(uint64_t dst, Hyprutils::OS::CFileDescriptor& fd); bool transfer(SP from, uint64_t fromPoint, uint64_t toPoint); void signal(uint64_t point); int m_drmFD = -1; Hyprutils::OS::CFileDescriptor m_syncobjFD; uint32_t m_handle = 0; WP m_self; uint64_t m_acquirePoint = 0, m_releasePoint = 1; private: CSyncTimeline() = default; }; } hyprwm-hyprtoolkit-71515e8/src/resource/000077500000000000000000000000001513450241200203225ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/resource/assetCache/000077500000000000000000000000001513450241200223655ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/resource/assetCache/AssetCache.cpp000066400000000000000000000011351513450241200250740ustar00rootroot00000000000000#include "AssetCache.hpp" using namespace Hyprtoolkit; using namespace Hyprtoolkit::Asset; SP Asset::assetCache() { static auto cache = makeShared(); return cache; } SP CAssetCache::get(const std::string_view& source) { for (const auto& e : m_entries) { if (e && e->source() == source) return e.lock(); } return nullptr; } void CAssetCache::cache(SP entry) { gc(); m_entries.emplace_back(entry); } void CAssetCache::gc() { std::erase_if(m_entries, [](const auto& e) { return !e; }); } hyprwm-hyprtoolkit-71515e8/src/resource/assetCache/AssetCache.hpp000066400000000000000000000020321513450241200250760ustar00rootroot00000000000000#pragma once #include "AssetCacheEntry.hpp" #include namespace Hyprtoolkit::Asset { // AssetCache is a cache for loading assets. Since we keep assets (images) as // textures in VRAM, we don't want to load the same raster asset multiple times. // IMPORTANT: AssetCache holds only WEAK references to the entries. You need // to hold your own ref somewhere to avoid the asset going dead class CAssetCache { public: CAssetCache() = default; ~CAssetCache() = default; CAssetCache(const CAssetCache&) = delete; CAssetCache(CAssetCache&) = delete; CAssetCache(CAssetCache&&) = delete; // TODO: implement this for svg images. These can be loaded at different res's SP get(const std::string_view& source); void cache(SP entry); private: void gc(); std::vector> m_entries; }; SP assetCache(); };hyprwm-hyprtoolkit-71515e8/src/resource/assetCache/AssetCacheEntry.cpp000066400000000000000000000013401513450241200261140ustar00rootroot00000000000000#include "AssetCacheEntry.hpp" using namespace Hyprtoolkit; using namespace Hyprtoolkit::Asset; CAssetCacheEntry::CAssetCacheEntry(const std::string_view& source, const SP tex) : m_source(source), m_tex(tex), m_status(CACHE_ENTRY_DONE) { ; } CAssetCacheEntry::CAssetCacheEntry(const std::string_view& source) : m_source(source) { ; } std::string_view CAssetCacheEntry::source() const { return m_source; } SP CAssetCacheEntry::tex() const { return m_tex; } eAssetCacheEntryStatus CAssetCacheEntry::status() const { return m_status; } void CAssetCacheEntry::texDone(SP tex) { m_tex = tex; m_status = CACHE_ENTRY_DONE; m_events.done.emit(); } hyprwm-hyprtoolkit-71515e8/src/resource/assetCache/AssetCacheEntry.hpp000066400000000000000000000025001513450241200261200ustar00rootroot00000000000000#pragma once #include #include #include "../../helpers/Memory.hpp" namespace Hyprtoolkit { class IRendererTexture; } namespace Hyprtoolkit::Asset { enum eAssetCacheEntryStatus : uint8_t { CACHE_ENTRY_PENDING = 0, CACHE_ENTRY_DONE = 1, }; class CAssetCacheEntry { public: CAssetCacheEntry(const std::string_view& source, const SP tex); CAssetCacheEntry(const std::string_view& source); ~CAssetCacheEntry() = default; CAssetCacheEntry(const CAssetCacheEntry&) = delete; CAssetCacheEntry(CAssetCacheEntry&) = delete; CAssetCacheEntry(CAssetCacheEntry&&) = delete; std::string_view source() const; SP tex() const; eAssetCacheEntryStatus status() const; // if created without a tex, this will mark asset as done void texDone(SP tex); bool operator==(const CAssetCacheEntry& e) const { return m_tex == e.m_tex; } struct { Hyprutils::Signal::CSignalT<> done; } m_events; private: const std::string m_source; SP m_tex; eAssetCacheEntryStatus m_status = CACHE_ENTRY_PENDING; }; };hyprwm-hyprtoolkit-71515e8/src/sessionLock/000077500000000000000000000000001513450241200207675ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/sessionLock/WaylandSessionLock.cpp000066400000000000000000000034111513450241200252460ustar00rootroot00000000000000#include "WaylandSessionLock.hpp" #include "../core/InternalBackend.hpp" #include "../core/platforms/WaylandPlatform.hpp" #include "../window/WaylandLockSurface.hpp" #include using namespace Hyprtoolkit; CWaylandSessionLockState::CWaylandSessionLockState(SP lock) : m_lock(lock) { m_lock->setLocked([this](CCExtSessionLockV1* r) { m_sessionLocked = true; }); m_lock->setFinished([this](CCExtSessionLockV1* r) { g_logger->log(HT_LOG_ERROR, "We got denied by the compositor to be the exclusive lock screen client. Is there another lock screen active?"); m_denied = true; m_events.finished.emit(); }); } void CWaylandSessionLockState::unlock() { if (m_sessionUnlocked) { g_logger->log(HT_LOG_WARNING, "Double unlock in WaylandSessionLockState!"); return; } if (!m_lock) { g_logger->log(HT_LOG_WARNING, "Unlock without an active sessionLock object in WaylandSessionLockState!"); return; } m_lock->sendUnlockAndDestroy(); m_lock.reset(); m_sessionUnlocked = true; // roundtrip in order to make sure we have unlocked before sending closeRequest wl_display_roundtrip(g_waylandPlatform->m_waylandState.display); for (const auto& sls : m_lockSurfaces) { if (sls.expired()) continue; sls->m_events.closeRequest.emit(); } m_lockSurfaces.clear(); } void CWaylandSessionLockState::onOutputRemoved(uint32_t outputHandle) { auto lockSurfaceIt = std::ranges::find_if(m_lockSurfaces, [outputHandle](const auto& other) { return other->m_outputHandle == outputHandle; }); if (lockSurfaceIt != m_lockSurfaces.end()) { (*lockSurfaceIt)->m_events.closeRequest.emit(); m_lockSurfaces.erase(lockSurfaceIt); } } hyprwm-hyprtoolkit-71515e8/src/sessionLock/WaylandSessionLock.hpp000066400000000000000000000016371513450241200252630ustar00rootroot00000000000000#pragma once #include "wayland.hpp" #include "../helpers/Memory.hpp" #include #include namespace Hyprtoolkit { class CWaylandLockSurface; class CWaylandSessionLockState : public ISessionLockState { public: CWaylandSessionLockState(SP lock); ~CWaylandSessionLockState() = default; virtual void unlock(); void onOutputRemoved(uint32_t outputHandle); std::vector> m_lockSurfaces; SP m_lock = nullptr; bool m_sessionLocked = false; bool m_sessionUnlocked = false; bool m_denied = false; // set on the finished event }; } hyprwm-hyprtoolkit-71515e8/src/system/000077500000000000000000000000001513450241200200175ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/system/DesktopMethods.cpp000066400000000000000000000006411513450241200234610ustar00rootroot00000000000000#include "DesktopMethods.hpp" #include using namespace Hyprtoolkit; using namespace Hyprtoolkit::DesktopMethods; using namespace Hyprutils::OS; std::expected DesktopMethods::openLink(const std::string_view& sv) { CProcess proc("xdg-open", {std::string{sv}}); if (!proc.runAsync()) return std::unexpected("Failed to spawn process"); return {}; } hyprwm-hyprtoolkit-71515e8/src/system/DesktopMethods.hpp000066400000000000000000000002521513450241200234640ustar00rootroot00000000000000#pragma once #include #include namespace Hyprtoolkit::DesktopMethods { std::expected openLink(const std::string_view& sv); };hyprwm-hyprtoolkit-71515e8/src/system/Icons.cpp000066400000000000000000000150251513450241200216010ustar00rootroot00000000000000#include "Icons.hpp" #include "../helpers/Memory.hpp" #include "../core/InternalBackend.hpp" #include #include #include #include #include #include #include extern "C" { #include "iniparser.h" } using namespace Hyprtoolkit; using namespace Hyprutils::String; using namespace Hyprutils::Utils; static std::optional readFileAsString(const std::string& path) { std::error_code ec; if (!std::filesystem::exists(path, ec) || ec) return std::nullopt; std::ifstream file(path); if (!file.good()) return std::nullopt; return trim(std::string((std::istreambuf_iterator(file)), (std::istreambuf_iterator()))); } static const std::array ICON_THEME_DIRS = {"/usr/share/icons", "/usr/local/share/icons", "~/.icons"}; static std::string fromDir(const char* p) { static auto HOME_ENV = getenv("HOME"); if (p[0] == '~') { if (!HOME_ENV) { g_logger->log(HT_LOG_ERROR, "CSystemIconFactory: can't resolve user path without $HOME"); return p; } return std::string{HOME_ENV} + std::string{p}.substr(1); } return p; } static std::optional getThemeDir(const std::string& name) { for (const auto& rawDir : ICON_THEME_DIRS) { auto path = fromDir(rawDir) + "/" + name; std::error_code ec; if (!std::filesystem::exists(path + "/index.theme", ec) || ec) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping theme dir {} (no index.theme)", path); continue; } return path; } return std::nullopt; } static std::optional findAnyTheme() { // first, try to find the default system theme for (const auto& rawDir : ICON_THEME_DIRS) { auto path = fromDir(rawDir) + "/default/index.theme"; std::error_code ec; if (!std::filesystem::exists(path) || ec) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping file {} (no access)", path); continue; } if (!std::filesystem::is_regular_file(path, ec) || ec) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping file {} (not a file)", path); continue; } dictionary* ini = iniparser_load(path.c_str()); CScopeGuard x([ini] { iniparser_freedict(ini); }); if (!ini) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping file {} (iniparser failed)", path); continue; } auto iconTheme = iniparser_getstring(ini, "Icon Theme:Inherits", nullptr); if (iconTheme) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: Theme {} has valid data", path); // try to find it auto themeDir = getThemeDir(iconTheme); if (themeDir && !themeDir->empty()) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: Found {} as default fallback", themeDir.value()); return themeDir; } g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: Skipping finding default theme, as {} inherits a non-existent theme", path); break; } else g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: Skipping {}, doesn't inherit an icon theme", path); } for (const auto& rawDir : ICON_THEME_DIRS) { auto path = fromDir(rawDir) + "/"; std::error_code ec; if (!std::filesystem::exists(path) || ec) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping dir {} (no access)", path); continue; } if (!std::filesystem::is_directory(path, ec) || ec) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping dir {} (not a dir)", path); continue; } for (const auto& p : std::filesystem::directory_iterator(path)) { if (!std::filesystem::exists(p.path().string() + "/index.theme", ec) || ec) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: skipping theme dir {} (no index.theme)", p.path().string()); continue; } return path; } } return std::nullopt; } static std::optional getIconThemeDir() { static std::optional path; static bool once = true; if (!once) return path; once = false; // if we have an explicit one, try to find it. if (!g_palette->m_vars.iconTheme.empty()) path = getThemeDir(g_palette->m_vars.iconTheme); if (path) { g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: Using theme dir {}, explicit by user", *path); return path; } // we haven't found one, or it's unset. Use the first one we can. path = findAnyTheme(); if (path) g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: Using theme dir {} (default fallback)", *path); else g_logger->log(HT_LOG_ERROR, "CSystemIconFactory: No theme found, icons will be broken"); return path; } CSystemIconFactory::CSystemIconFactory() : m_themeDir(getIconThemeDir()) { // Parse the index.theme if applicable and find Directories if (!m_themeDir) return; const auto THEME_DATA = readFileAsString(*m_themeDir + "/index.theme"); if (!THEME_DATA) return; size_t directoriesEntry = THEME_DATA->find("\nDirectories="); if (directoriesEntry == std::string::npos) directoriesEntry = THEME_DATA->find("\nDirectories ="); if (directoriesEntry == std::string::npos) { g_logger->log(HT_LOG_ERROR, "CSystemIconFactory: index.theme has no Directories?"); return; } size_t directoriesLineEnd = THEME_DATA->find('\n', directoriesEntry + 2); directoriesEntry = THEME_DATA->find('=', directoriesEntry + 2) + 1; std::string directoriesList = THEME_DATA->substr(directoriesEntry, directoriesLineEnd - directoriesEntry); CConstVarList dirs(directoriesList, 0, ',', true); m_iconDirs.reserve(dirs.size()); for (const auto& d : dirs) { m_iconDirs.emplace_back(trim(std::string{d})); } g_logger->log(HT_LOG_TRACE, "CSystemIconFactory: index.theme parsed: {} dirs", dirs.size()); } SP CSystemIconFactory::lookupIcon(const std::string& iconName) { if (!m_themeDir) return makeShared(); return makeShared(iconName); }hyprwm-hyprtoolkit-71515e8/src/system/Icons.hpp000066400000000000000000000021641513450241200216060ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprtoolkit { class CSystemIconDescription : public ISystemIconDescription { public: // invalid icon CSystemIconDescription(); // lookup icon CSystemIconDescription(const std::string& name); virtual ~CSystemIconDescription() = default; virtual bool exists(); virtual bool scalable(); std::string m_bestPath = ""; bool m_scalable = false; }; class CSystemIconFactory : public ISystemIconFactory { public: CSystemIconFactory(); virtual ~CSystemIconFactory() = default; /* Lookup an icon. If the icon is found, will return associated data. This object can be used to create an ImageElement */ virtual Hyprutils::Memory::CSharedPointer lookupIcon(const std::string& iconName); private: std::optional m_themeDir; std::vector m_iconDirs; friend class CSystemIconDescription; }; }; hyprwm-hyprtoolkit-71515e8/src/system/SystemIcon.cpp000066400000000000000000000025201513450241200226170ustar00rootroot00000000000000#include "Icons.hpp" #include "../core/InternalBackend.hpp" #include using namespace Hyprtoolkit; CSystemIconDescription::CSystemIconDescription() { ; } CSystemIconDescription::CSystemIconDescription(const std::string& name) { if (g_iconFactory->m_iconDirs.empty()) return; const auto THEME_DIR = g_iconFactory->m_themeDir.value(); bool found = false; for (const auto& sd : g_iconFactory->m_iconDirs) { auto fullDirPath = THEME_DIR + "/"; fullDirPath += sd; std::error_code ec; if (!std::filesystem::exists(fullDirPath, ec) || ec) continue; auto iconPath = fullDirPath + "/"; iconPath += name + ".svg"; // FUCK them raster themes in the year of our Lord 2025 if (!std::filesystem::exists(iconPath, ec) || ec) continue; m_bestPath = iconPath; found = true; break; } if (!found) { // try /usr/share/pixmaps std::error_code ec; if (std::filesystem::exists("/usr/share/pixmaps/" + name + ".svg", ec) || ec) { found = true; m_bestPath = "/usr/share/pixmaps/" + name + ".svg"; } } } bool CSystemIconDescription::exists() { return !m_bestPath.empty(); } bool CSystemIconDescription::scalable() { return m_scalable; }hyprwm-hyprtoolkit-71515e8/src/types/000077500000000000000000000000001513450241200176375ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/types/FontSizeType.cpp000066400000000000000000000014061513450241200227470ustar00rootroot00000000000000#include #include "../core/InternalBackend.hpp" #include "../Macros.hpp" using namespace Hyprtoolkit; CFontSize::CFontSize(eSizingBase base, float mult) : m_base(base), m_value(mult) { ; } float CFontSize::ptSize() { switch (m_base) { case HT_FONT_H1: return g_palette->m_vars.h1Size * m_value; case HT_FONT_H2: return g_palette->m_vars.h2Size * m_value; case HT_FONT_H3: return g_palette->m_vars.h3Size * m_value; case HT_FONT_TEXT: return g_palette->m_vars.fontSize * m_value; case HT_FONT_SMALL: return g_palette->m_vars.smallFontSize * m_value; case HT_FONT_ABSOLUTE: return m_value; default: UNREACHABLE(); } UNREACHABLE(); return 1; // suppress warning }hyprwm-hyprtoolkit-71515e8/src/types/SizeType.cpp000066400000000000000000000016451513450241200221250ustar00rootroot00000000000000#include using namespace Hyprtoolkit; CDynamicSize::CDynamicSize(eSizingType typeX, eSizingType typeY, const Hyprutils::Math::Vector2D& size) : m_typeX(typeX), m_typeY(typeY), m_value(size) { ; } Hyprutils::Math::Vector2D CDynamicSize::calculate(Hyprutils::Math::Vector2D elSize) const { Hyprutils::Math::Vector2D size; switch (m_typeX) { case HT_SIZE_ABSOLUTE: size.x = m_value.x; break; case HT_SIZE_PERCENT: size.x = m_value.x * elSize.x; break; case HT_SIZE_AUTO: size.x = -1; break; default: break; } switch (m_typeY) { case HT_SIZE_ABSOLUTE: size.y = m_value.y; break; case HT_SIZE_PERCENT: size.y = m_value.y * elSize.y; break; case HT_SIZE_AUTO: size.y = -1; break; default: break; } return size; } bool CDynamicSize::hasAuto() { return m_typeX == HT_SIZE_AUTO || m_typeY == HT_SIZE_AUTO; } hyprwm-hyprtoolkit-71515e8/src/window/000077500000000000000000000000001513450241200200025ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/src/window/Builder.cpp000066400000000000000000000054651513450241200221060ustar00rootroot00000000000000#include "Window.hpp" #include "../core/InternalBackend.hpp" #include #include "ToolkitWindow.hpp" using namespace Hyprtoolkit; SP CWindowBuilder::begin() { SP p = SP(new CWindowBuilder()); p->m_data = makeUnique(); p->m_self = p; return p; } SP CWindowBuilder::type(eWindowType x) { m_data->type = x; return m_self.lock(); } SP CWindowBuilder::appTitle(std::string&& x) { m_data->title = std::move(x); return m_self.lock(); } SP CWindowBuilder::appClass(std::string&& x) { m_data->class_ = std::move(x); return m_self.lock(); } SP CWindowBuilder::preferredSize(const Hyprutils::Math::Vector2D& x) { m_data->preferredSize = x; return m_self.lock(); } SP CWindowBuilder::minSize(const Hyprutils::Math::Vector2D& x) { m_data->minSize = x; return m_self.lock(); } SP CWindowBuilder::maxSize(const Hyprutils::Math::Vector2D& x) { m_data->maxSize = x; return m_self.lock(); } SP CWindowBuilder::prefferedOutput(const SP& x) { m_data->prefferedOutputId = x->handle(); return m_self.lock(); } SP CWindowBuilder::parent(const SP& x) { m_data->parent = x; return m_self.lock(); } SP CWindowBuilder::pos(const Hyprutils::Math::Vector2D& x) { m_data->pos = x; return m_self.lock(); } SP CWindowBuilder::marginTopLeft(const Hyprutils::Math::Vector2D& x) { m_data->marginTopLeft = x; return m_self.lock(); } SP CWindowBuilder::marginBottomRight(const Hyprutils::Math::Vector2D& x) { m_data->marginBottomRight = x; return m_self.lock(); } SP CWindowBuilder::layer(uint32_t x) { m_data->layer = x; return m_self.lock(); } SP CWindowBuilder::anchor(uint32_t x) { m_data->anchor = x; return m_self.lock(); } SP CWindowBuilder::exclusiveEdge(uint32_t x) { m_data->exclusiveEdge = x; return m_self.lock(); } SP CWindowBuilder::exclusiveZone(int32_t x) { m_data->exclusiveZone = x; return m_self.lock(); } SP CWindowBuilder::kbInteractive(uint32_t x) { m_data->kbInteractive = x; return m_self.lock(); } SP CWindowBuilder::commence() { switch (m_data->type) { case HT_WINDOW_POPUP: if (!m_data->parent) return nullptr; return reinterpretPointerCast(m_data->parent)->openPopup(*m_data); case HT_WINDOW_TOPLEVEL: case HT_WINDOW_LAYER: case HT_WINDOW_LOCK_SURFACE: return g_backend->openWindow(*m_data); } return nullptr; } hyprwm-hyprtoolkit-71515e8/src/window/IWaylandWindow.cpp000066400000000000000000000227101513450241200234100ustar00rootroot00000000000000#include "IWaylandWindow.hpp" #include "WaylandPopup.hpp" #include #include "../element/Element.hpp" #include "../core/platforms/WaylandPlatform.hpp" #include "../core/InternalBackend.hpp" #include "../renderer/Renderer.hpp" #include "../renderer/sync/SyncTimeline.hpp" #include "../core/AnimationManager.hpp" #include "../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CWaylandBuffer::CWaylandBuffer(SP buffer) : m_buffer(buffer) { auto params = makeShared(g_waylandPlatform->m_waylandState.dmabuf->sendCreateParams()); if (!params) { g_logger->log(HT_LOG_ERROR, "WaylandBuffer: failed to query params"); return; } auto attrs = buffer->dmabuf(); for (int i = 0; i < attrs.planes; ++i) { params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); } m_waylandState.buffer = makeShared(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); m_waylandState.buffer->setRelease([this](CCWlBuffer* r) { m_pendingRelease = false; }); params->sendDestroy(); } CWaylandBuffer::~CWaylandBuffer() { if (m_timeline) m_timeline->signal(m_timeline->m_acquirePoint); if (m_waylandState.buffer && m_waylandState.buffer->resource()) m_waylandState.buffer->sendDestroy(); } bool CWaylandBuffer::good() { return m_waylandState.buffer && m_waylandState.buffer->resource(); } void IWaylandWindow::onScaleUpdate() { configure(m_waylandState.logicalSize, m_waylandState.serial); } void IWaylandWindow::configure(const Vector2D& size, uint32_t serial) { m_waylandState.logicalSize = size; m_waylandState.appliedScale = m_fractionalScale; m_waylandState.size = (size * m_fractionalScale).floor(); m_waylandState.viewport->sendSetDestination(m_waylandState.logicalSize.x, m_waylandState.logicalSize.y); m_waylandState.surface->sendSetBufferScale(1); resizeSwapchain(m_waylandState.size); damageEntire(); m_rootElement->reposition({0, 0, m_waylandState.logicalSize.x, m_waylandState.logicalSize.y}); } void IWaylandWindow::resizeSwapchain(const Vector2D& pixelSize) { m_damageRing.setSize(pixelSize); if (!m_waylandState.swapchain) m_waylandState.swapchain = Aquamarine::CSwapchain::create(g_waylandPlatform->m_allocator, g_backend->m_aqBackend->getImplementations().at(0)); m_waylandState.swapchain->reconfigure(Aquamarine::SSwapchainOptions{ .length = 2, .size = pixelSize, .format = g_waylandPlatform->m_dmabufFormats.at(0).drmFormat, }); for (size_t i = 0; i < m_waylandState.wlBuffers.size(); ++i) { m_waylandState.wlBuffers[i] = makeShared(m_waylandState.swapchain->next(nullptr)); } } void IWaylandWindow::onPreRender() { const bool ANY_REPOSITION = !m_needsReposition.empty(); IToolkitWindow::onPreRender(); if (!m_waylandState.wlBuffers[0] || !m_waylandState.wlBuffers[1]) return; if (!ANY_REPOSITION) return; // recheck opaque region // TODO: maybe traverse the entire tree? CRegion rg; for (const auto& ch : m_rootElement->impl->children) { auto opaque = ch->opaqueBox(); if (opaque.empty()) continue; opaque.translate(ch->impl->position.pos()); rg.add(opaque); } m_waylandState.lastOpaqueRegion = std::move(rg); if (!m_waylandState.lastOpaqueRegion->empty()) { auto region = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateRegion()); m_waylandState.lastOpaqueRegion->forEachRect([®ion](const pixman_box32_t rect) { region->sendAdd(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); m_waylandState.surface->sendSetOpaqueRegion(region.get()); } else m_waylandState.surface->sendSetOpaqueRegion(nullptr); } void IWaylandWindow::prepareExplicit(SP buffer) { if (!g_renderer->explicitSyncSupported() || !g_waylandPlatform->m_waylandState.syncobj) return; if (!m_waylandState.syncobjSurf) m_waylandState.syncobjSurf = makeShared(g_waylandPlatform->m_waylandState.syncobj->sendGetSurface(m_waylandState.surface->proxy())); auto sync = g_renderer->exportSync(buffer->m_buffer.lock()); if (!buffer->m_waylandState.syncTimeline) { buffer->m_waylandState.syncTimeline = makeShared(g_waylandPlatform->m_waylandState.syncobj->sendImportTimeline(sync->m_syncobjFD.get())); buffer->m_timeline = sync; } sync->m_acquirePoint = sync->m_releasePoint + 1; } void IWaylandWindow::submitExplicit(SP buffer) { if (!m_waylandState.syncobjSurf) return; buffer->m_firstTimeIgnoreSync = false; auto sync = g_renderer->exportSync(buffer->m_buffer.lock()); sync->m_releasePoint = sync->m_acquirePoint + 1; TRACE(g_logger->log(HT_LOG_TRACE, "wayland: Submitting points acq: {}, rel: {} for ES", sync->m_acquirePoint, sync->m_releasePoint)); m_waylandState.syncobjSurf->sendSetAcquirePoint(buffer->m_waylandState.syncTimeline.get(), sc(sync->m_acquirePoint >> 32), sc(sync->m_acquirePoint & 0xFFFFFFFF)); m_waylandState.syncobjSurf->sendSetReleasePoint(buffer->m_waylandState.syncTimeline.get(), sc(sync->m_releasePoint >> 32), sc(sync->m_releasePoint & 0xFFFFFFFF)); g_renderer->signalRenderPoint(sync); } void IWaylandWindow::render() { if (m_waylandState.frameCallback) { TRACE(g_logger->log(HT_LOG_TRACE, "wayland: skipping render, frame callback present")); return; } auto currentBuffer = m_waylandState.wlBuffers[m_waylandState.bufIdx]; m_waylandState.bufIdx = (m_waylandState.bufIdx + 1) % 2; onPreRender(); if (!currentBuffer) return; m_needsFrame = false; g_renderer->beginRendering(m_self.lock(), currentBuffer->m_buffer.lock()); prepareExplicit(currentBuffer); g_renderer->render(currentBuffer->m_firstTimeIgnoreSync); m_waylandState.frameCallback = makeShared(m_waylandState.surface->sendFrame()); m_waylandState.frameCallback->setDone([this](CCWlCallback* r, uint32_t frameTime) { onCallback(); }); m_damageRing.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN).forEachRect([this](const pixman_box32_t box) { m_waylandState.surface->sendDamageBuffer(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); }); submitExplicit(currentBuffer); g_renderer->endRendering(); m_waylandState.surface->sendAttach(currentBuffer->m_waylandState.buffer.get(), 0, 0); m_waylandState.surface->sendCommit(); // // print frame time if (Env::isTrace()) { auto dur = std::chrono::steady_clock::now() - m_lastFrame; auto durMs = std::chrono::duration_cast(dur).count() / 1000.F; g_logger->log(HT_LOG_TRACE, "wayland: last frame took {:.2f}ms, FPS: {:.2f}", durMs, 1000.F / durMs); m_lastFrame = std::chrono::steady_clock::now(); } m_needsFrame = m_needsFrame || g_animationManager->shouldTickForNext(); } void IWaylandWindow::onCallback() { m_waylandState.frameCallback.reset(); if (m_needsFrame) render(); } Hyprutils::Math::Vector2D IWaylandWindow::pixelSize() { return m_waylandState.size; } float IWaylandWindow::scale() { return m_fractionalScale; } void IWaylandWindow::setCursor(ePointerShape shape) { g_waylandPlatform->setCursor(shape); } SP IWaylandWindow::openPopup(const SWindowCreationData& data) { auto x = makeShared(data, reinterpretPointerCast(m_self.lock())); x->m_self = x; m_popups.emplace_back(x); return x; } void IWaylandWindow::mouseButton(const Input::eMouseButton button, bool state) { if (m_popups.empty() || !state || m_ignoreNextButtonEvent) { m_ignoreNextButtonEvent = false; IToolkitWindow::mouseButton(button, state); return; } for (const auto& p : m_popups) { if (p) p->close(); } m_popups.clear(); m_ignoreNextButtonEvent = true; } void IWaylandWindow::mouseMove(const Hyprutils::Math::Vector2D& local) { if (!m_popups.empty()) return; IToolkitWindow::mouseMove(local); } void IWaylandWindow::mouseAxis(const Input::eAxisAxis axis, float delta) { if (!m_popups.empty()) return; IToolkitWindow::mouseAxis(axis, delta); } void IWaylandWindow::setIMTo(const Hyprutils::Math::CBox& box, const std::string& str, size_t cursor) { if (!g_waylandPlatform->m_waylandState.imState.enabled) { g_waylandPlatform->m_waylandState.textInput->sendEnable(); g_waylandPlatform->m_waylandState.imState.enabled = true; } g_waylandPlatform->m_waylandState.textInput->sendSetCursorRectangle(box.x, box.y, box.w, box.h); g_waylandPlatform->m_waylandState.textInput->sendCommit(); m_currentInput = str; m_currentInputCursor = cursor; } void IWaylandWindow::resetIM() { if (g_waylandPlatform->m_waylandState.imState.enabled) { g_waylandPlatform->m_waylandState.textInput->sendDisable(); g_waylandPlatform->m_waylandState.imState.enabled = false; } m_currentInput = ""; m_currentInputCursor = 0; } hyprwm-hyprtoolkit-71515e8/src/window/IWaylandWindow.hpp000066400000000000000000000073671513450241200234300ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../helpers/Memory.hpp" #include "ToolkitWindow.hpp" #include #include #include #include #include #include #include namespace Hyprtoolkit { class CSyncTimeline; class CWaylandBuffer { public: CWaylandBuffer(SP buffer); ~CWaylandBuffer(); bool good(); bool m_pendingRelease = false; SP m_timeline; bool m_firstTimeIgnoreSync = true; struct { SP buffer; SP syncTimeline; } m_waylandState; Hyprutils::Memory::CWeakPointer m_buffer; }; class IWaylandWindow : public IToolkitWindow { public: virtual ~IWaylandWindow() = default; virtual Hyprutils::Math::Vector2D pixelSize(); virtual float scale(); virtual void render(); virtual void setCursor(ePointerShape shape); virtual SP openPopup(const SWindowCreationData& data); virtual void mouseMove(const Hyprutils::Math::Vector2D& local); virtual void mouseButton(const Input::eMouseButton button, bool state); virtual void mouseAxis(const Input::eAxisAxis axis, float delta); virtual void setIMTo(const Hyprutils::Math::CBox& box, const std::string& str, size_t cursor); virtual void resetIM(); virtual void onPreRender(); std::vector> m_popups; protected: virtual void onCallback(); virtual void onScaleUpdate(); virtual void configure(const Hyprutils::Math::Vector2D& size, uint32_t serial); virtual void resizeSwapchain(const Hyprutils::Math::Vector2D& pixelSize); void prepareExplicit(SP); void submitExplicit(SP); float m_fractionalScale = 1.0; bool m_open = false; bool m_ignoreNextButtonEvent = false; struct { SP surface; SP xdgSurface; SP xdgToplevel; SP frameCallback; SP syncobjSurf; std::array, 2> wlBuffers; SP swapchain; size_t bufIdx = 0; Hyprutils::Math::Vector2D size; Hyprutils::Math::Vector2D logicalSize; float appliedScale; SP fractional = nullptr; SP viewport = nullptr; uint32_t serial = 0; std::optional lastOpaqueRegion; } m_waylandState; std::chrono::steady_clock::time_point m_lastFrame = std::chrono::steady_clock::now(); friend class CWaylandPlatform; friend class CWaylandPopup; friend class CWaylandWindow; }; }hyprwm-hyprtoolkit-71515e8/src/window/ToolkitWindow.cpp000066400000000000000000000302221513450241200233220ustar00rootroot00000000000000#include "ToolkitWindow.hpp" #include "../core/InternalBackend.hpp" #include "../layout/Positioner.hpp" #include "../core/AnimationManager.hpp" #include "../element/Element.hpp" #include "../element/text/Text.hpp" #include "../element/rectangle/Rectangle.hpp" #include "../Macros.hpp" #include #include using namespace Hyprtoolkit; struct Hyprtoolkit::SToolkitWindowData { void lock(const Hyprutils::Math::Vector2D& coord = {}); void unlock(); size_t cursorFocusLocks = 0; WP self; }; void SToolkitWindowData::lock(const Hyprutils::Math::Vector2D& coord) { cursorFocusLocks++; if (cursorFocusLocks == 1) self->impl->m_externalEvents.mouseEnter.emit(coord); } void SToolkitWindowData::unlock() { RASSERT(cursorFocusLocks, "SToolkitWindowData::unlock() on no locks"); cursorFocusLocks--; if (!cursorFocusLocks && !self->impl->window.expired()) self->impl->m_externalEvents.mouseLeave.emit(); } SToolkitFocusLock::SToolkitFocusLock(SP e, const Hyprutils::Math::Vector2D& coord) : m_el(e) { m_el->impl->toolkitWindowData->lock(coord); } SToolkitFocusLock::~SToolkitFocusLock() { if (m_el) m_el->impl->toolkitWindowData->unlock(); } void IToolkitWindow::damage(Hyprutils::Math::CRegion&& rg) { rg.scale(scale()); if (m_damageRing.damage(std::move(rg))) scheduleFrame(); } void IToolkitWindow::damageEntire() { m_damageRing.damageEntire(); scheduleFrame(); } void IToolkitWindow::scheduleFrame() { m_needsFrame = true; if (m_scheduledRender) { TRACE(g_logger->log(HT_LOG_TRACE, "scheduleFrame: skipping, already scheduled")); return; } TRACE(g_logger->log(HT_LOG_TRACE, "scheduleFrame: scheduling frame")); m_scheduledRender = true; g_backend->addIdle([this, self = m_self] { if (!self) return; TRACE(g_logger->log(HT_LOG_TRACE, "scheduleFrame: idle fired, rendering. Needs frame: {}", m_needsFrame)); m_scheduledRender = false; render(); }); } void IToolkitWindow::onPreRender() { g_animationManager->tick(); // simplify repositions: step 1, expand ancestors for (auto& e : m_needsReposition) { if (!e) continue; while (e->impl->parent && e->impl->parent->positioningDependsOnChild()) { e = e->impl->parent; } } // step 2: many will be repeated. Only calculate those that don't have any parent above already // scheduled std::erase_if(m_needsReposition, [this](WP e) { if (!e) return true; while (e->impl->parent) { if (std::ranges::find(m_needsReposition, e->impl->parent) != m_needsReposition.end()) return true; e = e->impl->parent.lock(); } return false; }); // step 3: eliminate same-parent nodes // since repositionNeeded will reposition the parent's children, // we don't need to do it 200 times if the parent has 200 children and all need a reposition std::vector> parents; parents.reserve(m_needsReposition.size()); std::erase_if(m_needsReposition, [&parents](WP e) { if (std::ranges::contains(parents, e->impl->parent)) return true; parents.emplace_back(e->impl->parent); return false; }); for (const auto& e : m_needsReposition) { g_positioner->repositionNeeded(e.lock()); } m_needsReposition.clear(); if (m_needsReposition.empty()) return; g_positioner->position(m_rootElement, {{}, pixelSize() / scale()}); } void IToolkitWindow::scheduleReposition(WP e) { m_needsReposition.emplace_back(e); scheduleFrame(); } void IToolkitWindow::initElementIfNeeded(SP e) { if (e->impl->toolkitWindowData) return; e->impl->toolkitWindowData = makeUnique(); e->impl->toolkitWindowData->self = e; } void IToolkitWindow::updateFocus(const Hyprutils::Math::Vector2D& coords) { m_mousePos = coords; SP el; std::vector> alwaysHover; m_rootElement->impl->breadthfirst([this, &el, &alwaysHover, coords](SP current) { if (current->acceptsMouseInput() && current->impl->position.containsPoint(coords)) { // check for any parent being a clip auto parent = current->impl->parent; while (parent) { if (parent->impl->clipChildren && !parent->impl->position.containsPoint(coords)) return; // nope parent = parent->impl->parent; } el = current; if (current->alwaysGetMouseInput()) { initElementIfNeeded(el); alwaysHover.emplace_back(makeShared(el, coords - el->impl->position.pos())); } } }); m_hoveredElements = alwaysHover; if ((el == (m_mainHoverElement ? m_mainHoverElement->m_el : WP{})) || m_mouseIsDown /* Lock focus while mouse is down */) { if (m_pointerFn) setCursor(m_pointerFn()); return; } if (el) { initElementIfNeeded(el); m_mainHoverElement = makeShared(el, coords - el->impl->position.pos()); } else m_mainHoverElement.reset(); if (m_mainHoverElement && m_mainHoverElement->m_el) { if (auto fn = m_mainHoverElement->m_el->pointerShapeFn(); fn) m_pointerFn = fn; else { setCursor(m_mainHoverElement->m_el->pointerShape()); m_pointerFn = nullptr; } } else { setCursor(HT_POINTER_ARROW); m_pointerFn = nullptr; } if (m_pointerFn) setCursor(m_pointerFn()); if (m_mainHoverElement && m_mainHoverElement->m_el && m_mainHoverElement->m_el->impl->hasTooltip && !m_tooltip.hoverTooltipTimer) { m_tooltip.hoverTooltipTimer = g_backend->addTimer( std::chrono::milliseconds(1000), [this, self = m_self](ASP s, void*) { if (!self || !m_mainHoverElement || !m_mainHoverElement->m_el || !m_mainHoverElement->m_el->impl->hasTooltip) return; openTooltip(m_mainHoverElement->m_el->impl->tooltip, m_mousePos); }, nullptr); } else m_tooltip.hoverTooltipTimer.reset(); } void IToolkitWindow::mouseEnter(const Hyprutils::Math::Vector2D& local) { updateFocus(local); if (m_mainHoverElement && m_mainHoverElement->m_el) m_mainHoverElement->m_el->impl->m_externalEvents.mouseMove.emit(local - m_mainHoverElement->m_el->impl->position.pos()); for (const auto& e : m_hoveredElements) { if (!e->m_el) continue; e->m_el->impl->m_externalEvents.mouseMove.emit(local - e->m_el->impl->position.pos()); } } void IToolkitWindow::mouseMove(const Hyprutils::Math::Vector2D& local) { updateFocus(local); if (m_tooltip.tooltipPopup) closeTooltip(); if (m_tooltip.hoverTooltipTimer) m_tooltip.hoverTooltipTimer->updateTimeout(std::chrono::seconds(1)); if (m_mainHoverElement && m_mainHoverElement->m_el) m_mainHoverElement->m_el->impl->m_externalEvents.mouseMove.emit(local - m_mainHoverElement->m_el->impl->position.pos()); for (const auto& e : m_hoveredElements) { if (!e->m_el) continue; e->m_el->impl->m_externalEvents.mouseMove.emit(local - e->m_el->impl->position.pos()); } } void IToolkitWindow::mouseButton(const Input::eMouseButton button, bool state) { m_mouseIsDown = state; if (state) { if (m_mainHoverElement && m_mainHoverElement->m_el && m_mainHoverElement->m_el->acceptsKeyboardInput() && m_keyboardFocus != m_mainHoverElement->m_el) { // enter this element if (m_keyboardFocus) m_keyboardFocus->impl->m_externalEvents.keyboardLeave.emit(); m_keyboardFocus = m_mainHoverElement->m_el; m_keyboardFocus->impl->m_externalEvents.keyboardEnter.emit(); } else if (!m_mainHoverElement || m_keyboardFocus != m_mainHoverElement->m_el) { if (m_keyboardFocus) m_keyboardFocus->impl->m_externalEvents.keyboardLeave.emit(); m_keyboardFocus.reset(); } } if (m_mainHoverElement && m_mainHoverElement->m_el) m_mainHoverElement->m_el->impl->m_externalEvents.mouseButton.emit(button, state); for (const auto& e : m_hoveredElements) { if (!e->m_el) continue; e->m_el->impl->m_externalEvents.mouseButton.emit(button, state); } } void IToolkitWindow::mouseAxis(const Input::eAxisAxis axis, float delta) { if (m_mainHoverElement && m_mainHoverElement->m_el) m_mainHoverElement->m_el->impl->m_externalEvents.mouseAxis.emit(axis, delta); for (const auto& e : m_hoveredElements) { if (!e->m_el) continue; e->m_el->impl->m_externalEvents.mouseAxis.emit(axis, delta); } } void IToolkitWindow::mouseLeave() { m_mainHoverElement.reset(); m_hoveredElements.clear(); } void IToolkitWindow::keyboardKey(const Input::SKeyboardKeyEvent& ev) { m_events.keyboardKey.emit(ev); if (!m_keyboardFocus) return; m_keyboardFocus->impl->m_externalEvents.key.emit(ev); } void IToolkitWindow::unfocusKeyboard() { if (!m_keyboardFocus) return; m_keyboardFocus->impl->m_externalEvents.keyboardLeave.emit(); m_keyboardFocus.reset(); } void IToolkitWindow::setKeyboardFocus(SP e) { unfocusKeyboard(); if (!e->acceptsKeyboardInput()) return; m_keyboardFocus = e; e->impl->m_externalEvents.keyboardEnter.emit(); } void IToolkitWindow::setIMTo(const Hyprutils::Math::CBox& box, const std::string& str, size_t cursor) { ; } void IToolkitWindow::resetIM() { ; } Hyprutils::Math::Vector2D IToolkitWindow::cursorPos() { return m_mousePos; } void IToolkitWindow::openTooltip(const std::string& s, const Hyprutils::Math::Vector2D& pos) { if (m_tooltip.tooltipPopup) return; constexpr const double MAX_TOOLTIP_WIDTH = 400; m_tooltip.text = CTextBuilder::begin()->color([] { return g_palette->m_colors.text; })->clampSize({MAX_TOOLTIP_WIDTH, -1.F})->text(std::string{s})->commence(); m_tooltip.bg = CRectangleBuilder::begin() ->color([] { return g_palette->m_colors.base; }) ->borderThickness(1) ->borderColor([] { return g_palette->m_colors.base.brighten(0.2F); }) ->rounding(g_palette->m_vars.smallRounding) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}) ->commence(); m_tooltip.bg->setReceivesMouse(true); m_tooltip.bg->setMouseLeave([self = m_self, this]() { if (!self) return; closeTooltip(); }); m_tooltip.text->setPositionMode(IElement::HT_POSITION_ABSOLUTE); m_tooltip.text->setPositionFlag(IElement::HT_POSITION_FLAG_CENTER, true); m_tooltip.bg->addChild(m_tooltip.text); const auto EXPECTED_TEXT_SIZE = m_tooltip.text->m_impl->getTextSizePreferred(); m_tooltip.tooltipPopup = reinterpretPointerCast(CWindowBuilder::begin() ->type(eWindowType::HT_WINDOW_POPUP) ->pos(pos) ->preferredSize(EXPECTED_TEXT_SIZE + Hyprutils::Math::Vector2D{30, 10}) ->parent(m_self.lock()) ->commence()); if (!m_tooltip.tooltipPopup) { g_logger->log(HT_LOG_ERROR, "IToolkitWindow: couldn't open tooltip"); return; } m_tooltip.tooltipPopup->m_rootElement->addChild(m_tooltip.bg); m_tooltip.tooltipPopup->open(); } void IToolkitWindow::closeTooltip() { if (!m_tooltip.tooltipPopup) return; m_tooltip.tooltipPopup->close(); m_tooltip.tooltipPopup.reset(); } hyprwm-hyprtoolkit-71515e8/src/window/ToolkitWindow.hpp000066400000000000000000000071641513450241200233400ustar00rootroot00000000000000#pragma once #include #include #include "../helpers/DamageRing.hpp" #include "../helpers/Memory.hpp" #include "../core/Input.hpp" #include "Window.hpp" namespace Hyprtoolkit { class CRectangleElement; class CColumnLayoutElement; class CTextElement; class CTimer; struct SToolkitFocusLock { SToolkitFocusLock(SP e, const Hyprutils::Math::Vector2D& coord); ~SToolkitFocusLock(); WP m_el; }; class IToolkitWindow : public IWindow { public: IToolkitWindow() = default; virtual ~IToolkitWindow() = default; /* Schedules a frame event as well. Takes logical coordinates (unscaled) */ virtual void damage(Hyprutils::Math::CRegion&& rg); virtual void scheduleFrame(); virtual void damageEntire(); virtual Hyprutils::Math::Vector2D cursorPos(); virtual void onPreRender(); virtual void render() = 0; virtual void scheduleReposition(WP e); virtual SP openPopup(const SWindowCreationData& data) = 0; virtual void mouseEnter(const Hyprutils::Math::Vector2D& local); virtual void mouseMove(const Hyprutils::Math::Vector2D& local); virtual void mouseButton(const Input::eMouseButton button, bool state); virtual void mouseAxis(const Input::eAxisAxis axis, float delta); virtual void mouseLeave(); virtual void keyboardKey(const Input::SKeyboardKeyEvent& e); virtual void unfocusKeyboard(); virtual void setKeyboardFocus(SP); virtual void updateFocus(const Hyprutils::Math::Vector2D& coords); virtual void setCursor(ePointerShape shape) = 0; virtual void setIMTo(const Hyprutils::Math::CBox& box, const std::string& str, size_t cursor); virtual void resetIM(); virtual void openTooltip(const std::string& s, const Hyprutils::Math::Vector2D& pos); virtual void closeTooltip(); void initElementIfNeeded(SP); // Damage ring is in pixel coords CDamageRing m_damageRing; bool m_needsFrame = true; WP m_self; Hyprutils::Math::Vector2D m_mousePos; bool m_mouseIsDown = false; std::string m_currentInput = ""; size_t m_currentInputCursor = 0; SP m_mainHoverElement; std::vector> m_hoveredElements; WP m_keyboardFocus; bool m_scheduledRender = false; std::function m_pointerFn = nullptr; std::vector> m_needsReposition; struct { SP tooltipPopup; SP bg; SP text; ASP hoverTooltipTimer; } m_tooltip; }; } hyprwm-hyprtoolkit-71515e8/src/window/WaylandLayer.cpp000066400000000000000000000126321513450241200231060ustar00rootroot00000000000000#include "WaylandLayer.hpp" #include "WaylandPopup.hpp" #include #include "../element/Element.hpp" #include "../core/platforms/WaylandPlatform.hpp" #include "../core/InternalBackend.hpp" #include "../renderer/Renderer.hpp" #include "../core/AnimationManager.hpp" #include "../Macros.hpp" #include "../output/WaylandOutput.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CWaylandLayer::CWaylandLayer(const SWindowCreationData& data) : m_creationData(data) { m_rootElement = CNullBuilder::begin()->commence(); } CWaylandLayer::~CWaylandLayer() { close(); if (g_waylandPlatform) std::erase_if(g_waylandPlatform->m_layers, [this](const auto& e) { return e.get() == this; }); } void CWaylandLayer::open() { if (m_open) return; m_open = true; if (!m_creationData.preferredSize) { g_logger->log(HT_LOG_ERROR, "layer opening failed: no preferred size."); return; } m_rootElement->impl->window = m_self; m_rootElement->impl->breadthfirst([this](SP e) { e->impl->window = m_self; }); if (!m_waylandState.surface) { m_waylandState.surface = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateSurface()); if (!m_waylandState.surface->resource()) { g_logger->log(HT_LOG_ERROR, "layer opening failed: no surface given. Errno: {}", errno); return; } auto inputRegion = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); m_waylandState.surface->sendSetInputRegion(inputRegion.get()); m_waylandState.fractional = makeShared(g_waylandPlatform->m_waylandState.fractional->sendGetFractionalScale(m_waylandState.surface->resource())); m_waylandState.fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) { const bool SAMESCALE = m_fractionalScale == scale / 120.0; m_fractionalScale = scale / 120.0; g_logger->log(HT_LOG_DEBUG, "layer: got fractional scale: {:.1f}%", m_fractionalScale * 100.F); if (!SAMESCALE && m_layerState.configured) onScaleUpdate(); }); m_waylandState.viewport = makeShared(g_waylandPlatform->m_waylandState.viewporter->sendGetViewport(m_waylandState.surface->resource())); } else m_waylandState.surface->sendAttach(nullptr, 0, 0); wl_proxy* outputProxy = nullptr; if (m_creationData.prefferedOutputId) { if (auto output = g_waylandPlatform->outputForHandle(m_creationData.prefferedOutputId); output) outputProxy = output->m_wlOutput->proxy(); } m_layerState.layerSurface = makeShared(g_waylandPlatform->m_waylandState.layerShell->sendGetLayerSurface( m_waylandState.surface->proxy(), outputProxy, sc(m_creationData.layer), m_creationData.class_.c_str())); if (!m_layerState.layerSurface->resource()) { g_logger->log(HT_LOG_ERROR, "layer opening failed: no ls resource. Errno: {}", errno); return; } m_layerState.layerSurface->sendSetSize(m_creationData.preferredSize->x, m_creationData.preferredSize->y); m_layerState.layerSurface->sendSetAnchor(sc(m_creationData.anchor)); m_layerState.layerSurface->sendSetExclusiveZone(m_creationData.exclusiveZone); m_layerState.layerSurface->sendSetMargin(m_creationData.marginTopLeft.y, m_creationData.marginBottomRight.x, m_creationData.marginBottomRight.y, m_creationData.marginTopLeft.x); m_layerState.layerSurface->sendSetKeyboardInteractivity(sc(m_creationData.kbInteractive)); if (m_creationData.exclusiveEdge > 0) m_layerState.layerSurface->sendSetExclusiveEdge(sc(m_creationData.exclusiveEdge)); m_waylandState.surface->sendCommit(); m_layerState.layerSurface->setConfigure([this](CCZwlrLayerSurfaceV1* r, uint32_t serial, uint32_t w, uint32_t h) { g_logger->log(HT_LOG_DEBUG, "wayland: configure layer with {}x{}", w, h); m_layerState.configured = true; if (w == 0 || h == 0) { if (m_creationData.preferredSize) { w = m_creationData.preferredSize->x; h = m_creationData.preferredSize->y; } else { g_logger->log(HT_LOG_DEBUG, "configure: w/h is 0, sending default hardcoded 1280x720"); w = 1280; h = 720; } } if (m_waylandState.logicalSize == Vector2D{sc(w), sc(h)}) return; m_layerState.layerSurface->sendAckConfigure(serial); configure(Vector2D{sc(w), sc(h)}, m_waylandState.serial); m_events.resized.emit(m_waylandState.logicalSize); }); m_layerState.layerSurface->setClosed([this](CCZwlrLayerSurfaceV1* r) { close(); m_events.layerClosed.emit(); }); } void CWaylandLayer::close() { if (!m_open) return; m_open = false; m_waylandState.frameCallback.reset(); m_layerState.layerSurface.reset(); m_waylandState.logicalSize = {}; m_layerState.configured = false; } void CWaylandLayer::render() { if (!m_layerState.configured) return; IWaylandWindow::render(); } hyprwm-hyprtoolkit-71515e8/src/window/WaylandLayer.hpp000066400000000000000000000012151513450241200231060ustar00rootroot00000000000000#pragma once #include "IWaylandWindow.hpp" #include namespace Hyprtoolkit { class CWaylandLayer : public IWaylandWindow { public: CWaylandLayer(const SWindowCreationData& data); virtual ~CWaylandLayer(); virtual void close(); virtual void open(); virtual void render(); private: SWindowCreationData m_creationData; struct { SP layerSurface; bool configured = false; } m_layerState; friend class CWaylandPlatform; friend class CWaylandPopup; }; };hyprwm-hyprtoolkit-71515e8/src/window/WaylandLockSurface.cpp000066400000000000000000000106321513450241200242310ustar00rootroot00000000000000#include "WaylandLockSurface.hpp" #include #include #include #include "../core/AnimationManager.hpp" #include "../core/InternalBackend.hpp" #include "../core/platforms/WaylandPlatform.hpp" #include "../element/Element.hpp" #include "../output/WaylandOutput.hpp" #include "../renderer/Renderer.hpp" #include "../sessionLock/WaylandSessionLock.hpp" #include "../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CWaylandLockSurface::CWaylandLockSurface(const SWindowCreationData& data) : m_outputHandle(data.prefferedOutputId) { m_rootElement = CNullBuilder::begin()->commence(); } CWaylandLockSurface::~CWaylandLockSurface() { close(); } void CWaylandLockSurface::open() { if (m_open) return; if (m_outputHandle == 0) { g_logger->log(HT_LOG_ERROR, "session lock missing prefferedOutputId"); return; } if (!g_waylandPlatform || !g_waylandPlatform->m_sessionLockState) { g_logger->log(HT_LOG_ERROR, "wayland platform not initialized"); return; } auto lockObject = g_waylandPlatform->m_sessionLockState->m_lock; if (!lockObject) return; auto wlOutput = g_waylandPlatform->outputForHandle(m_outputHandle); if (!wlOutput) return; m_open = true; m_rootElement->impl->window = m_self; m_rootElement->impl->breadthfirst([this](SP e) { e->impl->window = m_self; }); if (!m_waylandState.surface) { m_waylandState.surface = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateSurface()); if (!m_waylandState.surface->resource()) { g_logger->log(HT_LOG_ERROR, "lock surface opening failed: no surface given. Errno: {}", errno); return; } auto inputRegion = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); m_waylandState.surface->sendSetInputRegion(inputRegion.get()); m_waylandState.fractional = makeShared(g_waylandPlatform->m_waylandState.fractional->sendGetFractionalScale(m_waylandState.surface->resource())); m_waylandState.fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) { const bool SAMESCALE = m_fractionalScale == scale / 120.0; m_fractionalScale = scale / 120.0; g_logger->log(HT_LOG_DEBUG, "lock surface: got fractional scale: {:.1f}%", m_fractionalScale * 100.F); if (!SAMESCALE && m_lockSurfaceState.configured) onScaleUpdate(); }); m_waylandState.viewport = makeShared(g_waylandPlatform->m_waylandState.viewporter->sendGetViewport(m_waylandState.surface->resource())); } else m_waylandState.surface->sendAttach(nullptr, 0, 0); m_lockSurfaceState.lockSurface = makeShared(lockObject->sendGetLockSurface(m_waylandState.surface->resource(), wlOutput->m_wlOutput->resource())); if (!m_lockSurfaceState.lockSurface->resource()) { g_logger->log(HT_LOG_ERROR, "lock surface opening failed: no lock surface. Errno: {}", errno); return; } m_lockSurfaceState.lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t w, uint32_t h) { g_logger->log(HT_LOG_DEBUG, "wayland: configure layer with {}x{}", w, h); m_lockSurfaceState.configured = true; if (w == 0 || h == 0) { g_logger->log(HT_LOG_ERROR, "configure: w/h is 0, for a lock surface is bogus. Still, trying with 1920x1080..."); w = 1920; h = 1080; } if (m_waylandState.logicalSize == Vector2D{sc(w), sc(h)}) return; m_lockSurfaceState.lockSurface->sendAckConfigure(serial); configure(Vector2D{sc(w), sc(h)}, m_waylandState.serial); m_events.resized.emit(m_waylandState.logicalSize); }); } void CWaylandLockSurface::close() { if (!m_open) return; m_open = false; m_waylandState.frameCallback.reset(); m_lockSurfaceState.lockSurface.reset(); m_waylandState.logicalSize = {}; m_lockSurfaceState.configured = false; } void CWaylandLockSurface::render() { if (!m_lockSurfaceState.configured) return; IWaylandWindow::render(); } hyprwm-hyprtoolkit-71515e8/src/window/WaylandLockSurface.hpp000066400000000000000000000012031513450241200242300ustar00rootroot00000000000000#pragma once #include "IWaylandWindow.hpp" #include namespace Hyprtoolkit { class CWaylandLockSurface : public IWaylandWindow { public: CWaylandLockSurface(const SWindowCreationData& data); virtual ~CWaylandLockSurface(); virtual void close(); virtual void open(); virtual void render(); private: uint32_t m_outputHandle = 0; struct { SP lockSurface; bool configured = false; } m_lockSurfaceState; friend class CWaylandSessionLockState; }; }; hyprwm-hyprtoolkit-71515e8/src/window/WaylandPopup.cpp000066400000000000000000000124721513450241200231370ustar00rootroot00000000000000#include "WaylandPopup.hpp" #include "WaylandWindow.hpp" #include #include "../element/Element.hpp" #include "../core/platforms/WaylandPlatform.hpp" #include "../core/InternalBackend.hpp" #include "../renderer/Renderer.hpp" #include "../core/AnimationManager.hpp" #include "../layout/Positioner.hpp" #include "../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CWaylandPopup::CWaylandPopup(const SWindowCreationData& data, SP window) : m_parent(window), m_creationData(data) { m_rootElement = CNullBuilder::begin()->commence(); } CWaylandPopup::~CWaylandPopup() { close(); } void CWaylandPopup::open() { if (m_open) return; m_rootElement->impl->window = m_self; m_rootElement->impl->breadthfirst([this](SP e) { e->impl->window = m_self; }); m_open = true; m_parent->updateFocus(Vector2D{-100000, -100000}); m_waylandState.surface = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateSurface()); if (!m_waylandState.surface->resource()) { g_logger->log(HT_LOG_ERROR, "window opening failed: no surface given. Errno: {}", errno); return; } m_waylandState.xdgSurface = makeShared(g_waylandPlatform->m_waylandState.xdg->sendGetXdgSurface(m_waylandState.surface->resource())); if (!m_waylandState.xdgSurface->resource()) { g_logger->log(HT_LOG_ERROR, "window opening failed: no xdgSurface given. Errno: {}", errno); return; } m_waylandState.xdgSurface->setConfigure([this](CCXdgSurface* r, uint32_t serial) { g_logger->log(HT_LOG_DEBUG, "wayland window: configure surface with {}", serial); r->sendAckConfigure(serial); m_waylandState.serial = serial; }); m_wlPopupState.xdgPositioner = makeShared(g_waylandPlatform->m_waylandState.xdg->sendCreatePositioner()); m_wlPopupState.xdgPositioner->sendSetAnchorRect(m_creationData.pos.x, m_creationData.pos.y, 1, 1); m_wlPopupState.xdgPositioner->sendSetAnchor(XDG_POSITIONER_ANCHOR_TOP_LEFT); m_wlPopupState.xdgPositioner->sendSetGravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); m_wlPopupState.xdgPositioner->sendSetSize(m_creationData.preferredSize.value_or(Vector2D{200, 200}).x, m_creationData.preferredSize.value_or(Vector2D{200, 200}).y); m_wlPopupState.xdgPositioner->sendSetConstraintAdjustment( (xdgPositionerConstraintAdjustment)(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X)); m_wlPopupState.xdgPopup = makeShared(m_waylandState.xdgSurface->sendGetPopup(m_parent->m_waylandState.xdgSurface.get(), m_wlPopupState.xdgPositioner.get())); if (!m_wlPopupState.xdgPopup->resource()) { g_logger->log(HT_LOG_ERROR, "window opening failed: no xdgToplevel given. Errno: {}", errno); return; } m_wlPopupState.xdgPopup->setConfigure([this](CCXdgPopup* r, int32_t x, int32_t y, int32_t w, int32_t h) { g_logger->log(HT_LOG_DEBUG, "wayland: configure toplevel with {}x{}", w, h); if (!m_wlPopupState.grabbed) { m_wlPopupState.grabbed = true; m_wlPopupState.xdgPopup->sendGrab(g_waylandPlatform->m_waylandState.seat->resource(), g_waylandPlatform->m_lastEnterSerial); } if (m_waylandState.logicalSize == Vector2D{w, h}) return; configure({w, h}, m_waylandState.serial); m_events.resized.emit(m_waylandState.logicalSize); }); m_wlPopupState.xdgPopup->setPopupDone([this](CCXdgPopup* r) { close(); }); m_waylandState.fractional = makeShared(g_waylandPlatform->m_waylandState.fractional->sendGetFractionalScale(m_waylandState.surface->resource())); m_waylandState.fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) { const bool SAMESCALE = m_fractionalScale == scale / 120.0; m_fractionalScale = scale / 120.0; g_logger->log(HT_LOG_DEBUG, "window: got fractional scale: {:.1f}%", m_fractionalScale * 100.F); if (!SAMESCALE) onScaleUpdate(); }); m_waylandState.viewport = makeShared(g_waylandPlatform->m_waylandState.viewporter->sendGetViewport(m_waylandState.surface->resource())); auto inputRegion = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); m_waylandState.surface->sendSetInputRegion(inputRegion.get()); m_waylandState.surface->sendAttach(nullptr, 0, 0); m_waylandState.surface->sendCommit(); inputRegion->sendDestroy(); } void CWaylandPopup::close() { if (g_waylandPlatform->m_currentWindow == m_self) g_waylandPlatform->m_currentWindow = m_parent; if (m_parent) std::erase(m_parent->m_popups, m_self); if (!m_open) return; m_open = false; m_waylandState.frameCallback.reset(); if (m_wlPopupState.xdgPopup) m_wlPopupState.xdgPopup->sendDestroy(); if (m_waylandState.xdgSurface) m_waylandState.xdgSurface->sendDestroy(); if (m_waylandState.surface) m_waylandState.surface->sendDestroy(); m_waylandState = {}; m_events.popupClosed.emit(); } SP CWaylandPopup::openPopup(const SWindowCreationData& data) { return nullptr; //FIXME: } hyprwm-hyprtoolkit-71515e8/src/window/WaylandPopup.hpp000066400000000000000000000016211513450241200231360ustar00rootroot00000000000000#pragma once #include "IWaylandWindow.hpp" namespace Hyprtoolkit { class CWaylandWindow; // TODO: de-dup this shit class CWaylandPopup : public IWaylandWindow { public: CWaylandPopup(const SWindowCreationData& data, SP parent); virtual ~CWaylandPopup(); virtual void close(); virtual void open(); virtual Hyprutils::Memory::CSharedPointer openPopup(const SWindowCreationData& data); private: WP m_parent; SWindowCreationData m_creationData; struct { SP xdgPositioner; SP xdgPopup; bool grabbed = false; } m_wlPopupState; friend class CWaylandPlatform; friend class CWaylandWindow; }; };hyprwm-hyprtoolkit-71515e8/src/window/WaylandWindow.cpp000066400000000000000000000117201513450241200232760ustar00rootroot00000000000000#include "WaylandWindow.hpp" #include "WaylandPopup.hpp" #include #include "../element/Element.hpp" #include "../core/platforms/WaylandPlatform.hpp" #include "../core/InternalBackend.hpp" #include "../renderer/Renderer.hpp" #include "../core/AnimationManager.hpp" #include "../Macros.hpp" using namespace Hyprtoolkit; using namespace Hyprutils::Math; CWaylandWindow::CWaylandWindow(const SWindowCreationData& data) : m_creationData(data) { m_rootElement = CNullBuilder::begin()->commence(); } CWaylandWindow::~CWaylandWindow() { close(); if (g_waylandPlatform) std::erase_if(g_waylandPlatform->m_windows, [this](const auto& e) { return e.get() == this; }); } void CWaylandWindow::open() { if (m_open) return; m_open = true; m_rootElement->impl->window = m_self; m_rootElement->impl->breadthfirst([this](SP e) { e->impl->window = m_self; }); m_waylandState.surface = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateSurface()); if (!m_waylandState.surface->resource()) { g_logger->log(HT_LOG_ERROR, "window opening failed: no surface given. Errno: {}", errno); return; } m_waylandState.xdgSurface = makeShared(g_waylandPlatform->m_waylandState.xdg->sendGetXdgSurface(m_waylandState.surface->resource())); if (!m_waylandState.xdgSurface->resource()) { g_logger->log(HT_LOG_ERROR, "window opening failed: no xdgSurface given. Errno: {}", errno); return; } m_waylandState.xdgSurface->setConfigure([this](CCXdgSurface* r, uint32_t serial) { g_logger->log(HT_LOG_DEBUG, "wayland window: configure surface with {}", serial); r->sendAckConfigure(serial); m_waylandState.serial = serial; }); m_waylandState.xdgToplevel = makeShared(m_waylandState.xdgSurface->sendGetToplevel()); if (!m_waylandState.xdgToplevel->resource()) { g_logger->log(HT_LOG_ERROR, "window opening failed: no xdgToplevel given. Errno: {}", errno); return; } m_waylandState.xdgToplevel->setWmCapabilities([](CCXdgToplevel* r, wl_array* arr) { g_logger->log(HT_LOG_DEBUG, "window opening failed: wm_capabilities received"); }); m_waylandState.xdgToplevel->setConfigure([this](CCXdgToplevel* r, int32_t w, int32_t h, wl_array* arr) { g_logger->log(HT_LOG_DEBUG, "wayland: configure toplevel with {}x{}", w, h); if (w == 0 || h == 0) { if (m_creationData.preferredSize) { w = m_creationData.preferredSize->x; h = m_creationData.preferredSize->y; } else { g_logger->log(HT_LOG_DEBUG, "configure: w/h is 0, sending default hardcoded 1280x720"); w = 1280; h = 720; } } if (m_waylandState.logicalSize == Vector2D{w, h}) return; configure({w, h}, m_waylandState.serial); m_events.resized.emit(m_waylandState.logicalSize); }); m_waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { m_events.closeRequest.emit(); }); m_waylandState.fractional = makeShared(g_waylandPlatform->m_waylandState.fractional->sendGetFractionalScale(m_waylandState.surface->resource())); m_waylandState.fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) { const bool SAMESCALE = m_fractionalScale == scale / 120.0; m_fractionalScale = scale / 120.0; g_logger->log(HT_LOG_DEBUG, "window: got fractional scale: {:.1f}%", m_fractionalScale * 100.F); if (!SAMESCALE) onScaleUpdate(); }); m_waylandState.viewport = makeShared(g_waylandPlatform->m_waylandState.viewporter->sendGetViewport(m_waylandState.surface->resource())); m_waylandState.xdgToplevel->sendSetTitle(m_creationData.title.c_str()); m_waylandState.xdgToplevel->sendSetAppId(m_creationData.class_.c_str()); if (m_creationData.minSize) m_waylandState.xdgToplevel->sendSetMinSize(m_creationData.minSize->x, m_creationData.minSize->y); if (m_creationData.maxSize) m_waylandState.xdgToplevel->sendSetMaxSize(m_creationData.maxSize->x, m_creationData.maxSize->y); auto inputRegion = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); m_waylandState.surface->sendSetInputRegion(inputRegion.get()); m_waylandState.surface->sendAttach(nullptr, 0, 0); m_waylandState.surface->sendCommit(); inputRegion->sendDestroy(); } void CWaylandWindow::close() { if (!m_open) return; m_open = false; m_waylandState.frameCallback.reset(); if (m_waylandState.xdgToplevel) m_waylandState.xdgToplevel->sendDestroy(); if (m_waylandState.xdgSurface) m_waylandState.xdgSurface->sendDestroy(); if (m_waylandState.surface) m_waylandState.surface->sendDestroy(); m_waylandState = {}; } hyprwm-hyprtoolkit-71515e8/src/window/WaylandWindow.hpp000066400000000000000000000006571513450241200233120ustar00rootroot00000000000000#pragma once #include "IWaylandWindow.hpp" namespace Hyprtoolkit { class CWaylandWindow : public IWaylandWindow { public: CWaylandWindow(const SWindowCreationData& data); virtual ~CWaylandWindow(); virtual void close(); virtual void open(); private: SWindowCreationData m_creationData; friend class CWaylandPlatform; friend class CWaylandPopup; }; };hyprwm-hyprtoolkit-71515e8/src/window/Window.hpp000066400000000000000000000021741513450241200217660ustar00rootroot00000000000000#pragma once #include #include #include "../helpers/Memory.hpp" namespace Hyprtoolkit { struct SWindowCreationData { eWindowType type = HT_WINDOW_TOPLEVEL; std::optional preferredSize; std::optional minSize; std::optional maxSize; std::string title = "Hyprtoolkit App"; std::string class_ = "hyprtoolkit-app"; uint32_t prefferedOutputId = 0; // popups Hyprutils::Math::Vector2D pos; SP parent; // layers uint32_t layer = 0; uint32_t anchor = 0; int32_t exclusiveZone = 0; uint32_t exclusiveEdge = 0; Hyprutils::Math::Vector2D marginTopLeft, marginBottomRight; uint32_t kbInteractive = 0; }; }; hyprwm-hyprtoolkit-71515e8/tests/000077500000000000000000000000001513450241200170465ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/tests/Controls.cpp000066400000000000000000000252351513450241200213640ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::Memory; using namespace Hyprutils::Math; using namespace Hyprtoolkit; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer static SP backend; static SP hiddenSlider; static SP hiddenText; static SP mainLayout; static SP window; static SP popup; static SP textbox; constexpr float SLIDER_HEIGHT = 10.F; // static void toggleVisibilityOfSecretSlider() { static bool visible = false; if (!visible) mainLayout->addChild(hiddenSlider); else mainLayout->removeChild(hiddenSlider); visible = !visible; } static void toggleTextVisibility() { static bool visible = false; if (!visible) mainLayout->addChild(hiddenText); else mainLayout->removeChild(hiddenText); visible = !visible; } static void openPopup() { popup = CWindowBuilder::begin() // ->type(Hyprtoolkit::HT_WINDOW_POPUP) ->preferredSize({350, 600}) ->pos({200, 200}) ->parent(window) ->commence(); auto popbg = CRectangleBuilder::begin() ->rounding(10) ->color([] { return backend->getPalette()->m_colors.background.brighten(0.5); }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}}) ->commence(); auto poptext = CTextBuilder::begin() // ->text("THEY CALL ME MR BOOMBASTIC") ->fontSize({CFontSize::HT_FONT_H2}) ->color([] { return backend->getPalette()->m_colors.text; }) ->commence(); poptext->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); poptext->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true); popup->m_rootElement->addChild(popbg); popbg->addChild(poptext); popup->open(); popup->m_events.popupClosed.listenStatic([] { popup.reset(); }); } static void selectTextbox() { textbox->focus(); } static SP stretchLayout(std::string&& label, SP control) { auto bg = CRectangleBuilder::begin() ->color([] { return backend->getPalette()->m_colors.alternateBase; }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->rounding(4) ->commence(); auto layoutE = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); auto labelE = CTextBuilder::begin()->text(std::move(label))->color([] { return backend->getPalette()->m_colors.text; })->commence(); auto nullE = CNullBuilder::begin()->commence(); nullE->setGrow(true); auto container = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); container->setMargin(4); bg->addChild(container); container->addChild(layoutE); layoutE->addChild(labelE); layoutE->addChild(nullE); layoutE->addChild(control); return bg; } int main(int argc, char** argv, char** envp) { backend = IBackend::create(); // window = CWindowBuilder::begin() // ->preferredSize({480, 700}) ->minSize({480, 480}) ->maxSize({1280, 720}) ->appTitle("Controls") ->appClass("hyprtoolkit-controls") ->commence(); auto bg = CRectangleBuilder::begin()->color([] { return backend->getPalette()->m_colors.background; })->commence(); window->m_rootElement->addChild(bg); auto scroll = CScrollAreaBuilder::begin()->scrollY(true)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})->commence(); mainLayout = CColumnLayoutBuilder::begin()->gap(3)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.7F, 1.F}})->commence(); mainLayout->setMargin(3); mainLayout->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); mainLayout->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); bg->addChild(scroll); scroll->addChild(mainLayout); auto title = CTextBuilder::begin() // ->text("Controls") ->fontSize({CFontSize::HT_FONT_H2}) ->color([] { return backend->getPalette()->m_colors.text; }) ->commence(); title->setTooltip("Example tooltip!! Woo!"); auto hr = CRectangleBuilder::begin() // ->color([] { return CHyprColor{backend->getPalette()->m_colors.text.darken(0.65)}; }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, 9.F}}) ->commence(); hr->setMargin(4); auto button1 = CButtonBuilder::begin() ->label("Secret") ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->onMainClick([](SP) { toggleVisibilityOfSecretSlider(); }) ->commence(); auto button2 = CButtonBuilder::begin() ->label("Popup") ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->onMainClick([](SP) { openPopup(); }) ->commence(); auto button3 = CButtonBuilder::begin() ->label("Select textbox") ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->onMainClick([](SP) { selectTextbox(); }) ->commence(); auto button4 = CButtonBuilder::begin() ->label("Show text") ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->onMainClick([](SP) { toggleTextVisibility(); }) ->commence(); auto checkbox = stretchLayout("checkbox 1", CCheckboxBuilder::begin()->commence()); auto checkbox2 = stretchLayout("checkbox 2", CCheckboxBuilder::begin()->commence()); auto spinbox = CSpinboxBuilder::begin() ->label("Spinbox") ->items({"Hello", "World", "Amongus"}) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->fill(true) ->commence(); auto slider = stretchLayout("Slider", CSliderBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, SLIDER_HEIGHT}})->commence()); auto slider2 = stretchLayout( "Big Slider", CSliderBuilder::begin()->max(10000)->val(2500)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, SLIDER_HEIGHT}})->commence()); auto combo = stretchLayout( "Combo", CComboboxBuilder::begin() ->items({"According", "to", "all", "known", "laws", "of", "aviation", "there", "is", "no", "way", "that", "a", "bee", "should", "be", "able", "to", "fly.", "its", "wings", "are", "too", "small", "to", "get", "its", "fat", "little", "body", "off", "the", "ground.", "the", "bee", "of", "course", "flies", "anyways,", "because", "bees", "don't", "care", "what", "humans", "think", "is", "impossible."}) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.3F, 25.F}}) ->commence()); textbox = CTextboxBuilder::begin()->defaultText("")->placeholder("placeholder")->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {150.F, 24.F}})->commence(); auto textboxCont = stretchLayout("Textbox", textbox); auto text = CTextBuilder::begin() ->text("This is a link test: click me! Test overflow as well woo woo woo woo woo woo woo woo I am vaxry") ->commence(); text->setTooltip("Example tooltip!"); hiddenSlider = CSliderBuilder::begin()->max(100)->val(69)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, SLIDER_HEIGHT}})->commence(); hiddenText = CTextBuilder::begin() ->text("hi hi overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow") ->commence(); auto rowl = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); rowl->addChild(CTextBuilder::begin()->text("hello this will be ellipsized woooo oooo ooo oo oo oo oo o oo")->commence()); mainLayout->addChild(title); mainLayout->addChild(hr); mainLayout->addChild(button1); mainLayout->addChild(button2); mainLayout->addChild(button3); mainLayout->addChild(button4); mainLayout->addChild(checkbox); mainLayout->addChild(checkbox2); mainLayout->addChild(spinbox); mainLayout->addChild(slider); mainLayout->addChild(slider2); mainLayout->addChild(combo); mainLayout->addChild(textboxCont); mainLayout->addChild(text); mainLayout->addChild(rowl); auto iconDesc = backend->systemIcons()->lookupIcon("action-unavailable-symbolic"); if (!iconDesc->exists()) iconDesc = backend->systemIcons()->lookupIcon("action-unavailable"); if (iconDesc->exists()) mainLayout->addChild(CImageBuilder::begin()->icon(iconDesc)->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {40, 40}})->commence()); window->m_events.closeRequest.listenStatic([w = WP{window}] { w->close(); backend->destroy(); }); window->open(); backend->enterLoop(); return 0; }hyprwm-hyprtoolkit-71515e8/tests/Dialog.cpp000066400000000000000000000077201513450241200207570ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::Memory; using namespace Hyprutils::Math; using namespace Hyprtoolkit; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer static SP backend; int main(int argc, char** argv, char** envp) { backend = IBackend::create(); // auto window = CWindowBuilder::begin()->preferredSize({480, 180})->minSize({480, 180})->maxSize({480, 180})->appTitle("Dialog")->appClass("hyprtoolkit-dialog")->commence(); window->m_rootElement->addChild(CRectangleBuilder::begin()->color([] { return backend->getPalette()->m_colors.background; })->commence()); auto layout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})->commence(); layout->setMargin(3); auto layoutInner = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.85F, 1.F}})->commence(); window->m_rootElement->addChild(layout); layout->addChild(layoutInner); layoutInner->setGrow(true); layoutInner->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); layoutInner->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); auto title = CTextBuilder::begin()->text("Hello World")->fontSize({CFontSize::HT_FONT_H2})->color([] { return backend->getPalette()->m_colors.text; })->commence(); auto hr = CRectangleBuilder::begin() // ->color([] { return CHyprColor{backend->getPalette()->m_colors.text.darken(0.65)}; }) ->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, 9.F}}) ->commence(); hr->setMargin(4); auto content = CTextBuilder::begin() ->text("This is an example dialog. This first line is long on purpose, so that we overflow. Amogus sussus biggus sussus.\n\nWoo!") ->align(Hyprtoolkit::HT_FONT_ALIGN_CENTER) ->color([] { return backend->getPalette()->m_colors.text; }) ->commence(); content->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); auto button1 = CButtonBuilder::begin() ->label("Exit") ->onMainClick([w = WP{window}](SP el) { w->close(); backend->destroy(); }) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->commence(); auto button2 = CButtonBuilder::begin() ->label("Do something") ->onMainClick([](SP el) { std::println("Did something!"); }) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->commence(); auto null2 = CNullBuilder::begin()->commence(); auto layout2 = CRowLayoutBuilder::begin()->gap(3)->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); null2->setGrow(true); layoutInner->addChild(title); layoutInner->addChild(hr); layoutInner->addChild(content); layout2->addChild(null2); layout2->addChild(button2); layout2->addChild(button1); layout->addChild(layout2); window->m_events.closeRequest.listenStatic([w = WP{window}] { w->close(); backend->destroy(); }); window->open(); backend->enterLoop(); return 0; }hyprwm-hyprtoolkit-71515e8/tests/SimpleSessionLock.cpp000066400000000000000000000127071513450241200231670ustar00rootroot00000000000000#include "hyprtoolkit/core/SessionLock.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::Memory; using namespace Hyprutils::Math; using namespace Hyprtoolkit; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer static SP backend; static SP lockState; static std::vector> windows; static void addTimer(SP rect) { backend->addTimer( std::chrono::seconds(1), [rect](Hyprutils::Memory::CAtomicSharedPointer timer, void* data) { rect->rebuild()->color([] { return CHyprColor{rand() % 1000 / 1000.F, rand() % 1000 / 1000.F, rand() % 1000 / 1000.F, 1.F}; })->commence(); addTimer(rect); }, nullptr); } static void layout(const SP& window) { window->m_rootElement->addChild(CRectangleBuilder::begin()->color([] { return CHyprColor{0.1F, 0.1F, 0.1F}; })->commence()); auto layout = CRowLayoutBuilder::begin()->commence(); layout->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); layout->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); layout->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_VCENTER, true); window->m_rootElement->addChild(layout); auto rect3 = CRectangleBuilder::begin() // ->color([] { return CHyprColor{0.2F, 0.4F, 0.4F}; }) ->rounding(10) ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {150, 150}}) ->commence(); auto rect4 = CRectangleBuilder::begin() // ->color([] { return CHyprColor{0.4F, 0.2F, 0.4F}; }) ->rounding(0) ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {50, 50}}) ->commence(); auto layout2 = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(); auto image = CImageBuilder::begin() // ->path("/home/max/media/picture/avatar.png") ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {447, 447}}) ->commence(); auto text = CTextBuilder::begin()->text("never give up")->color([] { return CHyprColor{0.4F, 0.4F, 0.4F}; })->commence(); auto button = CButtonBuilder::begin() ->label("Click to unlock") ->onMainClick([](SP el) { el->rebuild()->label("Unlocking...")->commence(); lockState->unlock(); }) ->onRightClick([](SP el) { el->rebuild()->label("Reset")->commence(); }) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->commence(); text->setGrow(true); rect4->setGrow(true); addTimer(rect4); layout2->addChild(image); layout2->addChild(button); layout2->addChild(text); layout->addChild(layout2); layout->addChild(rect3); layout->addChild(rect4); window->open(); } static void closeWindow(WP window) { std::println("Remove surface {}!", (uintptr_t)windows.back().get()); auto windowsIt = std::ranges::find_if(windows, [&window](const auto& w) { return w.get() == window.get(); }); if (windowsIt == windows.end()) return; (*windowsIt)->close(); windows.erase(windowsIt); if (windows.empty()) { std::println("It's over!!!"); backend->destroy(); } } static void createLockSurface(SP output) { windows.emplace_back(CWindowBuilder::begin()->type(HT_WINDOW_LOCK_SURFACE)->prefferedOutput(output)->commence()); std::println("New surface {}!", (uintptr_t)windows.back().get()); WP weakWindow = windows.back(); weakWindow->m_events.closeRequest.listenStatic([weakWindow]() { closeWindow(weakWindow); }); layout(weakWindow.lock()); } int main(int argc, char** argv, char** envp) { int unlockSecs = 10; if (argc == 2) unlockSecs = atoi(argv[1]); backend = IBackend::create(); if (!backend) { std::println("Backend create failed!"); return 1; } auto sessionLockState = backend->aquireSessionLock(); if (sessionLockState.has_value()) lockState = sessionLockState.value(); if (!lockState) { std::println("Cloudn't lock"); return 1; } lockState->m_events.finished.listenStatic([] { std::println("Compositor kicked us"); for (const auto& w : windows) { closeWindow(w); } }); backend->m_events.outputAdded.listenStatic(createLockSurface); for (const auto& o : backend->getOutputs()) { createLockSurface(o); } backend->addTimer(std::chrono::seconds(unlockSecs), [](auto, auto) { lockState->unlock(); }, nullptr); backend->enterLoop(); return 0; } hyprwm-hyprtoolkit-71515e8/tests/SimpleWindow.cpp000066400000000000000000000066451513450241200222060ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::Memory; using namespace Hyprutils::Math; using namespace Hyprtoolkit; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer static SP backend; static size_t buttonClicks = 1; static void addTimer(SP rect) { backend->addTimer( std::chrono::seconds(1), [rect](Hyprutils::Memory::CAtomicSharedPointer timer, void* data) { rect->rebuild()->color([] { return CHyprColor{rand() % 1000 / 1000.F, rand() % 1000 / 1000.F, rand() % 1000 / 1000.F, 1.F}; })->commence(); addTimer(rect); }, nullptr); } int main(int argc, char** argv, char** envp) { backend = IBackend::create(); // auto window = CWindowBuilder::begin()->preferredSize({640, 480})->appTitle("Hello World!")->appClass("hyprtoolkit")->commence(); window->m_rootElement->addChild(CRectangleBuilder::begin()->color([] { return CHyprColor{0.1F, 0.1F, 0.1F}; })->commence()); auto layout = CRowLayoutBuilder::begin()->commence(); window->m_rootElement->addChild(layout); auto rect3 = CRectangleBuilder::begin() // ->color([] { return CHyprColor{0.2F, 0.4F, 0.4F}; }) ->rounding(10) ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {150, 150}}) ->commence(); auto rect4 = CRectangleBuilder::begin() // ->color([] { return CHyprColor{0.4F, 0.2F, 0.4F}; }) ->rounding(10) ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {50, 50}}) ->commence(); auto layout2 = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(); auto image = CImageBuilder::begin() // ->path("/home/vaxry/Documents/born to C++.png") ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {447, 447}}) ->commence(); auto text = CTextBuilder::begin()->text("world is a fuck")->color([] { return CHyprColor{0.4F, 0.4F, 0.4F}; })->commence(); auto button = CButtonBuilder::begin() ->label("Click me bitch") ->onMainClick([](SP el) { el->rebuild()->label(std::format("Clicked {} times bitch", buttonClicks++))->commence(); }) ->onRightClick([](SP el) { el->rebuild()->label("Reset bitch")->commence(); }) ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) ->commence(); text->setGrow(true); rect4->setGrow(true); addTimer(rect4); layout2->addChild(image); layout2->addChild(button); layout2->addChild(text); layout->addChild(layout2); layout->addChild(rect3); layout->addChild(rect4); window->open(); backend->enterLoop(); return 0; }hyprwm-hyprtoolkit-71515e8/tests/unit/000077500000000000000000000000001513450241200200255ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/tests/unit/elements/000077500000000000000000000000001513450241200216415ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/tests/unit/elements/Text.cpp000066400000000000000000000010531513450241200232700ustar00rootroot00000000000000#include #include #include #include "../tricks/Tricks.hpp" using namespace Hyprtoolkit; TEST(Element, text) { Tests::Tricks::createBackendSupport(); auto text = CTextBuilder::begin()->text(R"(Hello link! Hi link2!)")->commence(); EXPECT_EQ(text->m_impl->parsedText, "Hello link! Hi link2!"); text.reset(); }hyprwm-hyprtoolkit-71515e8/tests/unit/elements/Textbox.cpp000066400000000000000000000101661513450241200240060ustar00rootroot00000000000000#include #include #include #include #include "../tricks/Tricks.hpp" #include "element/Element.hpp" #include "hyprtoolkit/core/Input.hpp" using namespace Hyprtoolkit; TEST(Element, textboxNavigation) { Tests::Tricks::createBackendSupport(); auto textbox = CTextboxBuilder::begin()->defaultText("is 🧑‍🌾 the same as 🧑🌾?")->commence(); EXPECT_EQ(textbox->cursorPos(), 0); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Right}); EXPECT_EQ(textbox->cursorPos(), 1); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Right}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Right}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Right}); EXPECT_EQ(textbox->cursorPos(), 7); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_End}); EXPECT_EQ(textbox->cursorPos(), 36); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Left}); EXPECT_EQ(textbox->cursorPos(), 35); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Left}); EXPECT_EQ(textbox->cursorPos(), 31); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Left, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->cursorPos(), 27); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Left, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->cursorPos(), 24); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Home}); EXPECT_EQ(textbox->cursorPos(), 0); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Right, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->cursorPos(), 2); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Right, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->cursorPos(), 14); textbox.reset(); } TEST(Element, textboxEditing) { Tests::Tricks::createBackendSupport(); auto textbox = CTextboxBuilder::begin()->defaultText("is 🧑‍🌾 the same as 🧑🌾?")->commence(); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete}); EXPECT_EQ(textbox->currentText(), "🌾 the same as 🧑🌾?"); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->currentText(), " the same as 🧑🌾?"); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_Delete, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->currentText(), " same as 🧑🌾?"); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_End}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_BackSpace}); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_BackSpace}); EXPECT_EQ(textbox->currentText(), " same as 🧑"); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_BackSpace, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->currentText(), " same as "); textbox->impl->m_externalEvents.key.emit(Input::SKeyboardKeyEvent{.xkbKeysym = XKB_KEY_BackSpace, .modMask = Input::HT_MODIFIER_CTRL}); EXPECT_EQ(textbox->currentText(), " same "); textbox.reset(); } hyprwm-hyprtoolkit-71515e8/tests/unit/helpers/000077500000000000000000000000001513450241200214675ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/tests/unit/helpers/Env.cpp000066400000000000000000000007761513450241200227350ustar00rootroot00000000000000#include #include using namespace Hyprtoolkit; TEST(Env, enabled) { setenv("HT_INTERNAL_TEST1", "1", 1); setenv("HT_INTERNAL_TEST2", "SUS", 1); setenv("HT_INTERNAL_TEST3", "0", 1); setenv("HT_INTERNAL_TEST4", "", 1); EXPECT_EQ(Env::envEnabled("HT_INTERNAL_TEST1"), true); EXPECT_EQ(Env::envEnabled("HT_INTERNAL_TEST2"), true); EXPECT_EQ(Env::envEnabled("HT_INTERNAL_TEST3"), false); EXPECT_EQ(Env::envEnabled("HT_INTERNAL_TEST4"), false); }hyprwm-hyprtoolkit-71515e8/tests/unit/helpers/UTF8.cpp000066400000000000000000000115671513450241200227330ustar00rootroot00000000000000#include #include using namespace Hyprtoolkit; TEST(UTF8, codepointLen) { const auto str = std::string("世 hello 🧑‍🌾"); EXPECT_EQ(UTF8::codepointLen(&str[0], str.length()), 3); EXPECT_EQ(UTF8::codepointLen(&str[3], str.length() - 3), 1); EXPECT_EQ(UTF8::codepointLen(&str[10], str.length() - 10), 4); } TEST(UTF8, codepointLenBefore) { const auto str = std::string("世 hello 🧑‍🌾"); EXPECT_EQ(UTF8::codepointLenBefore(str, 0), 0); EXPECT_EQ(UTF8::codepointLenBefore(str, 3), 3); EXPECT_EQ(UTF8::codepointLenBefore(str, 10), 1); EXPECT_EQ(UTF8::codepointLenBefore(str, 14), 4); } TEST(UTF8, length) { EXPECT_EQ(UTF8::length(""), 0); EXPECT_EQ(UTF8::length("Hello"), 5); EXPECT_EQ(UTF8::length("世界"), 2); EXPECT_EQ(UTF8::length("世界is酷薄"), 6); } TEST(UTF8, substr) { EXPECT_EQ(UTF8::substr("", 0, 0), ""); EXPECT_EQ(UTF8::substr("死", 1), ""); EXPECT_EQ(UTF8::substr("死", 0, 0), ""); EXPECT_EQ(UTF8::substr("Hello", 0, 3), "Hel"); EXPECT_EQ(UTF8::substr("世界", 1, 1), "界"); EXPECT_EQ(UTF8::substr("世界is酷薄", 1), "界is酷薄"); EXPECT_EQ(UTF8::substr("ハイパーランド", 1, 2), "イパ"); } TEST(UTF8, utf8ToOffset) { EXPECT_EQ(UTF8::utf8ToOffset("", 0), 0); EXPECT_EQ(UTF8::utf8ToOffset("Hello", 20000), 5); EXPECT_EQ(UTF8::utf8ToOffset("Hello", 3), 3); EXPECT_EQ(UTF8::utf8ToOffset("魑魅魍魎", 3), 9); EXPECT_EQ(UTF8::utf8ToOffset("a魑魅魍魎", 3), 7); } TEST(UTF8, offsetToUTF8Len) { EXPECT_EQ(UTF8::offsetToUTF8Len("", 0), 0); EXPECT_EQ(UTF8::offsetToUTF8Len("Hello", 3), 3); EXPECT_EQ(UTF8::offsetToUTF8Len("魑魅魍魎", 3), 1); EXPECT_EQ(UTF8::offsetToUTF8Len("a魑魅魍魎", 4), 2); EXPECT_EQ(UTF8::offsetToUTF8Len("魑a魅", 1), 1); } TEST(UTF8, findFirstOf) { // weird values EXPECT_EQ(UTF8::findFirstOf("", ""), std::string::npos); EXPECT_EQ(UTF8::findFirstOf("", "aba"), std::string::npos); EXPECT_EQ(UTF8::findFirstOf("", "aba", 1000), std::string::npos); EXPECT_EQ(UTF8::findFirstOf("aba", ""), std::string::npos); EXPECT_EQ(UTF8::findFirstOf("aba", "", 1000), std::string::npos); EXPECT_EQ(UTF8::findFirstOf("aba", "a", 1000), std::string::npos); const auto str = "is 🧑‍🌾 the same as 🧑🌾?"; EXPECT_EQ(UTF8::findFirstOf(str, "i", 2), std::string::npos); EXPECT_EQ(UTF8::findFirstOf(str, "I"), std::string::npos); EXPECT_EQ(UTF8::findFirstOf(str, "i"), 0); EXPECT_EQ(UTF8::findFirstOf(str, "?"), 35); EXPECT_EQ(UTF8::findFirstOf(str, "🧑"), 3); } TEST(UTF8, findLastOf) { // weird values EXPECT_EQ(UTF8::findLastOf("", ""), std::string::npos); EXPECT_EQ(UTF8::findLastOf("", "aba"), std::string::npos); EXPECT_EQ(UTF8::findLastOf("", "aba", 1000), std::string::npos); EXPECT_EQ(UTF8::findLastOf("aba", ""), std::string::npos); EXPECT_EQ(UTF8::findLastOf("aba", "", 1000), std::string::npos); EXPECT_EQ(UTF8::findLastOf("aba", "a", 1000), 2); const auto str = "is 🧑‍🌾 the same as 🧑🌾?"; EXPECT_EQ(UTF8::findLastOf(str, "i", 2), 0); EXPECT_EQ(UTF8::findLastOf(str, "I"), std::string::npos); EXPECT_EQ(UTF8::findLastOf(str, "i"), 0); EXPECT_EQ(UTF8::findLastOf(str, "?"), 35); EXPECT_EQ(UTF8::findLastOf(str, "🧑"), 27); } TEST(UTF8, findFirstNotOf) { // weird values EXPECT_EQ(UTF8::findFirstNotOf("", ""), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("", "aba"), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("", "aba", 1000), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("aba", "", 1000), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("aba", "a", 1000), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("aba", ""), 0); EXPECT_EQ(UTF8::findFirstNotOf("is?", "i", 2), 2); EXPECT_EQ(UTF8::findFirstNotOf("is?", "i"), 1); EXPECT_EQ(UTF8::findFirstNotOf("is?", "?", 2), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("is?", "?"), 0); EXPECT_EQ(UTF8::findFirstNotOf("bbbbb", "b"), std::string::npos); EXPECT_EQ(UTF8::findFirstNotOf("🧑🧑🌾🌾🧑", "🧑"), 8); } TEST(UTF8, findLastNotOf) { // weird values EXPECT_EQ(UTF8::findLastNotOf("", ""), std::string::npos); EXPECT_EQ(UTF8::findLastNotOf("", "aba"), std::string::npos); EXPECT_EQ(UTF8::findLastNotOf("", "aba", 1000), std::string::npos); EXPECT_EQ(UTF8::findLastNotOf("aba", "", 1000), 2); EXPECT_EQ(UTF8::findLastNotOf("aba", "a", 1000), 1); EXPECT_EQ(UTF8::findLastNotOf("aba", ""), 2); EXPECT_EQ(UTF8::findLastNotOf("is?", "i", 3), 2); EXPECT_EQ(UTF8::findLastNotOf("is?", "i"), 2); EXPECT_EQ(UTF8::findLastNotOf("is?", "?", 2), 1); EXPECT_EQ(UTF8::findLastNotOf("is?", "?"), 1); EXPECT_EQ(UTF8::findLastNotOf("bbbbb", "b"), std::string::npos); EXPECT_EQ(UTF8::findLastNotOf("🧑🧑🌾🌾🧑", "🧑"), 12); } hyprwm-hyprtoolkit-71515e8/tests/unit/positioner/000077500000000000000000000000001513450241200222205ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/tests/unit/positioner/Positioner.cpp000066400000000000000000000111741513450241200250630ustar00rootroot00000000000000 // FIXME: These tests aren't very comprehensive. #include #include #include #include #include #include using namespace Hyprtoolkit; using namespace Hyprutils::Math; TEST(Positioner, main) { auto root = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1000, 1000}})->commence(); root->setMargin(4); auto rowLayoutMain = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->gap(10)->commence(); root->addChild(rowLayoutMain); auto columnLayoutLeft = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->gap(10)->commence(); auto childLeft = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {100, 200}})->commence(); auto nullRight = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence(); nullRight->setGrow(true); auto columnLayoutRight = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(10)->commence(); auto childRight = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {200, 300}})->commence(); auto childRight2 = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {200, 300}})->commence(); childRight->setPositionMode(IElement::HT_POSITION_ABSOLUTE); childRight2->setPositionMode(IElement::HT_POSITION_ABSOLUTE); columnLayoutRight->setPositionMode(IElement::HT_POSITION_ABSOLUTE); nullRight->addChild(columnLayoutRight); rowLayoutMain->addChild(columnLayoutLeft); rowLayoutMain->addChild(nullRight); columnLayoutLeft->addChild(childLeft); columnLayoutRight->addChild(childRight); columnLayoutRight->addChild(childRight2); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(root->impl->position, CBox(4, 4, 992, 992)); EXPECT_EQ(rowLayoutMain->impl->position, CBox(4, 4, 992, 992)); EXPECT_EQ(columnLayoutLeft->impl->position, CBox(4, 4, 100, 992)); EXPECT_EQ(columnLayoutRight->impl->position, CBox(114, 4, 200, 610)); EXPECT_EQ(childRight2->impl->position, CBox(114, 314, 200, 300)); } TEST(Positioner, align) { auto root = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1000, 1000}})->commence(); auto child = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {10, 10}})->commence(); child->setPositionMode(IElement::HT_POSITION_ABSOLUTE); root->addChild(child); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(0, 0, 10, 10)); child->setPositionFlag(IElement::HT_POSITION_FLAG_VCENTER, true); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(0, 495, 10, 10)); child->setPositionFlag(IElement::HT_POSITION_FLAG_HCENTER, true); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(495, 495, 10, 10)); child->setPositionFlag(IElement::HT_POSITION_FLAG_VCENTER, false); child->setPositionFlag(IElement::HT_POSITION_FLAG_TOP, true); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(495, 0, 10, 10)); child->setPositionFlag(IElement::HT_POSITION_FLAG_TOP, false); child->setPositionFlag(IElement::HT_POSITION_FLAG_BOTTOM, true); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(495, 990, 10, 10)); child->setPositionFlag(IElement::HT_POSITION_FLAG_HCENTER, false); child->setPositionFlag(IElement::HT_POSITION_FLAG_RIGHT, true); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(990, 990, 10, 10)); child->setPositionFlag(IElement::HT_POSITION_FLAG_RIGHT, false); child->setPositionFlag(IElement::HT_POSITION_FLAG_LEFT, true); g_positioner->position(root, {{}, {1000, 1000}}); g_positioner->positionChildren(root); EXPECT_EQ(child->impl->position, CBox(0, 990, 10, 10)); }hyprwm-hyprtoolkit-71515e8/tests/unit/tricks/000077500000000000000000000000001513450241200213245ustar00rootroot00000000000000hyprwm-hyprtoolkit-71515e8/tests/unit/tricks/Tricks.cpp000066400000000000000000000011341513450241200232660ustar00rootroot00000000000000#include "Tricks.hpp" #include "core/AnimationManager.hpp" #include #include #include using namespace Hyprtoolkit::Tests::Tricks; using namespace Hyprtoolkit::Tests; using namespace Hyprutils::Memory; void Tricks::createBackendSupport() { g_logger = makeShared(); g_config = makeShared(); g_config->parse(); g_palette = CPalette::palette(); g_iconFactory = SP(new CSystemIconFactory()); g_animationManager = makeShared(); } hyprwm-hyprtoolkit-71515e8/tests/unit/tricks/Tricks.hpp000066400000000000000000000002511513450241200232720ustar00rootroot00000000000000#pragma once namespace Hyprtoolkit::Tests::Tricks { // doesn't make a backend but initializes needed stuff for elements to work void createBackendSupport(); };