From e2330df40dc5fbf112275511bdf48a74c9400cee Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Sun, 8 Sep 2024 12:37:09 -0500 Subject: [PATCH] Add documentation for examples and additional enhancements. - Document the titlebar, custom styles, and other examples. - Document custom widget overrides like sliders, dials, and LCD numbers. - Convert the Qt5 refs to Qt6. - Add in better links to examples in the README. - Add a changelog. - Make our custom widgets, like titlebars and sliders, to use a general-purpose class and then reusable components. - Ensure we use a shallow clone for our CMake builds. --- .github/workflows/lint.yml | 152 ++++---- CHANGELOG.md | 2 + README.md | 3 +- assets/advanced_docking_system_example.png | Bin 0 -> 45538 bytes assets/custom-standard-icons.png | Bin 0 -> 25917 bytes assets/custom_placeholder_text.png | Bin 0 -> 4085 bytes assets/custom_slider.png | Bin 0 -> 4703 bytes assets/custom_url.png | Bin 0 -> 5057 bytes assets/default-standard-icons.png | Bin 0 -> 37262 bytes assets/gallery.md | 68 ++-- assets/mismatched_titlebar.png | Bin 0 -> 5940 bytes example/README.md | 327 ++++++++++++++++++ example/branchless/README.md | 31 -- .../branchless/{application.py => main.py} | 5 +- example/cmake/breeze.cmake | 1 + .../system_theme.hpp} | 2 +- .../system_theme.py} | 2 +- example/{ => dial}/dial.py | 54 +-- example/dial/main.py | 58 ++++ example/{standard_icons.py => icons/main.py} | 149 ++------ example/icons/standard.py | 87 +++++ example/{ => lcd}/lcd.py | 78 +---- example/lcd/main.py | 74 ++++ example/placeholder_text.py | 1 + example/shared.py | 42 ++- example/slider/main.py | 55 +++ example/{ => slider}/slider.py | 49 +-- example/titlebar/main.py | 140 ++++++++ example/{ => titlebar}/titlebar.py | 56 +-- example/url.py | 1 + example/whatsthis.py | 1 + extension/README.md | 39 +-- scripts/cmake.sh | 17 +- scripts/lint.sh | 4 +- scripts/test_theme.cpp | 2 +- scripts/theme.sh | 12 +- setup.cfg | 4 +- 37 files changed, 992 insertions(+), 524 deletions(-) create mode 100644 assets/advanced_docking_system_example.png create mode 100644 assets/custom-standard-icons.png create mode 100644 assets/custom_placeholder_text.png create mode 100644 assets/custom_slider.png create mode 100644 assets/custom_url.png create mode 100644 assets/default-standard-icons.png create mode 100644 assets/mismatched_titlebar.png create mode 100644 example/README.md delete mode 100644 example/branchless/README.md rename example/branchless/{application.py => main.py} (96%) rename example/{breeze_theme.hpp => detect/system_theme.hpp} (99%) rename example/{breeze_theme.py => detect/system_theme.py} (99%) rename example/{ => dial}/dial.py (88%) create mode 100644 example/dial/main.py rename example/{standard_icons.py => icons/main.py} (55%) create mode 100644 example/icons/standard.py rename example/{ => lcd}/lcd.py (54%) create mode 100644 example/lcd/main.py create mode 100644 example/slider/main.py rename example/{ => slider}/slider.py (73%) create mode 100644 example/titlebar/main.py rename example/{ => titlebar}/titlebar.py (97%) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e1b52c8..6b34dc6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,76 +1,76 @@ -name: Linters - -on: [push, pull_request] - -jobs: - lint-version-python: - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.11", "3.12"] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint flake8 pyright - - name: Analysing the code with pylint - run: | - scripts/lint.sh - - lint-os-python: - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.10"] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint flake8 pyright - - name: Analysing the code with pylint - run: | - scripts/lint.sh - - lint-cpp: - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install clang-tidy - - name: Analysing the code with clang-tidy - shell: bash - run: | - set -eux pipefail - - # Windows oddly requires C++20 support due to internal bugs. - if [[ "${RUNNER_OS}" == "Windows" ]]; then - extra_args="-extra-arg=-std=c++20" - passthrough="" - elif [[ "${RUNNER_OS}" == "macOS" ]]; then - # NOTE: The search paths aren't added by default, and we need C then C++ by default - # for our search. This makes the process easier. - extra_args="-extra-arg=-std=c++17 -extra-arg=--stdlib=libc++" - location="$(xcrun --show-sdk-path)" - passthrough="-I${location}/usr/include/c++/v1 -I${location}/usr/include" - else - extra_args="-extra-arg=-std=c++17" - passthrough="" - fi - clang-tidy -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus* ${extra_args} example/breeze_theme.hpp -- ${passthrough} +name: Linters + +on: [push, pull_request] + +jobs: + lint-version-python: + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.11", "3.12"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint flake8 pyright + - name: Analysing the code with pylint + run: | + scripts/lint.sh + + lint-os-python: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.10"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint flake8 pyright + - name: Analysing the code with pylint + run: | + scripts/lint.sh + + lint-cpp: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install clang-tidy + - name: Analysing the code with clang-tidy + shell: bash + run: | + set -eux pipefail + + # Windows oddly requires C++20 support due to internal bugs. + if [[ "${RUNNER_OS}" == "Windows" ]]; then + extra_args="-extra-arg=-std=c++20" + passthrough="" + elif [[ "${RUNNER_OS}" == "macOS" ]]; then + # NOTE: The search paths aren't added by default, and we need C then C++ by default + # for our search. This makes the process easier. + extra_args="-extra-arg=-std=c++17 -extra-arg=--stdlib=libc++" + location="$(xcrun --show-sdk-path)" + passthrough="-I${location}/usr/include/c++/v1 -I${location}/usr/include" + else + extra_args="-extra-arg=-std=c++17" + passthrough="" + fi + clang-tidy -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus* ${extra_args} example/detect/system_theme.hpp -- ${passthrough} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9786864..4166622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning]. - Configurable stylesheets via themes. - Custom extension support, such as the advanced docking system. - Compile Qt resource files (from [chaosink]). +- Documented support for CMake builds (from [ruilvo]). ### Changed @@ -61,3 +62,4 @@ and this project adheres to [Semantic Versioning]. [Inverted-E]: https://github.com/Inverted-E/ [eblade]: https://github.com/eblade/ [chaosink]: https://github.com/chaosink/ +[ruilvo]: https://github.com/ruilvo/ diff --git a/README.md b/README.md index 4fbebb9..7015ea0 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/Alexhuszagh/BreezeStyleSheets.git GIT_TAG origin/main GIT_PROGRESS ON + GIT_SHALLOW 1 USES_TERMINAL_DOWNLOAD TRUE) FetchContent_GetProperties(breeze_stylesheets) @@ -252,7 +253,7 @@ The support stylesheets include: - `auto` - `native` (the system native theme) -And any `-purple`, `-green`, etc. variants can also be used. `auto` will automatically detect if the system theme is light or dark and select the correct theme accordingly. The cross-platform way to detect the correct theme is using `get_theme` in either [Python](/example/breeze_theme.py) or [C++](/example/breeze_theme.hpp). Just include those stand-alone files in your project and you can select the desired theme based on the user's profile settings at startup. +And any `-purple`, `-green`, etc. variants can also be used. `auto` will automatically detect if the system theme is light or dark and select the correct theme accordingly. A recipe for a cross-platform way to detect the correct theme in Python or C++ is shown in our [System Theme Detection](/example/README.md#system-theme-detection). ## Features diff --git a/assets/advanced_docking_system_example.png b/assets/advanced_docking_system_example.png new file mode 100644 index 0000000000000000000000000000000000000000..627242abbda6536e5b1bca6d3fe54899124997b8 GIT binary patch literal 45538 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

9_)(yniW@#o*~#6YZy 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 Ykx6Qr9(&m<5co4$xz0H8+bxIw2W8F7ivR!s literal 0 HcmV?d00001 diff --git a/assets/custom-standard-icons.png b/assets/custom-standard-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0891761b99eb57d04c0aa7ab22fcd19eb03654 GIT binary patch literal 25917 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 literal 0 HcmV?d00001 diff --git a/assets/custom_slider.png b/assets/custom_slider.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ca17b4838f2c69567c2b20a492bede9bee401b GIT binary patch literal 4703 zcmcgw2~<*P`@iZ}mVV|;rcLECS&c1jxuhj#Wn_M(wkd)Onu?YS;-0xwW{ZwGPMMOX zqotOBrih}sHEx->WneB~O0I;6B!~k3uQqkg%$Yg=Z@&L>&*8v*-{*av-?RSiKIVmsX7JT{KNaG%~1d-O_3M5 zuL1AZ2cCon1HgvHm5)>_`l%^du9?Sp%9aa)ZaWe6yQc{*3rd) zI}NR5Cevbn=R{#jQE)adi|J8$NJtB_9Ty+#?S$|sYvfhRD=Wjp!oo7K*nU-dfsIF8 zoJ@S~_1s)i{0ONr{^j!km+uhaBm13Cc0k=De0<#Ir)P?bd0qZUa$wIv_ny&Ty9{sy z0s)8foqCgh>VY<#9O(Y(0T#R-1tzNKOgj$r^jB%hg+eFh;uFWyOR2B$Cvju#>9~D{ zhK9sTXznN!=O8GaVu|4>5{U%$Fb*(T*-=r+C*|>K;^im0%y<;T7uD5K8toJc#qUH# z1UD~~wHAb6s#mP-Qe6(Yi~=u036XcBvzRwQs;tz5mp#FN!W0U^)yk^1h)U-<;2lS_ zZmkF!lA(zz3LECTSTi#Lsu|C{CUolEpc5CGut`f9AnyJfYc{)@8o^P~+*CbrqeV0? zL&A+m$G7H)UV9*QMg5H_ojDfum{pj)F{~fD@LD;C<{oP-d+yCXG?!LUQPDzQv0d<7 z$EiXU5;5C-v-u_Y8Rm^ynT%|}G@I@3dnuVPf%c5qb3cvoc&rlZ(#p9Xi;xyWRMcZhtS9tGah@fOx$cYX& zuUi(>f7&OoSiCN)Q6)4)MvcUCYUbty$SCfxB^IuDm@ByF>G1Z%CTr&Fo|uH@_=q=D zF4>vi`(e9r9M+y7yaakMR*#BS>tdJJ35EKzbji|J}a9FFRA-Pq%2Z+NU^9dMOBYD+GZm%fYO{pLX6xXQNqHOqFOi1|lIsHfogCkctk0h20y) zZ5_qG#yg5q)vi+`J(_8RZCRB8^droHn`y%x1A!B@9s&kQC~1U=dN={mlZ{YR?I-bc zII(WtV)&n6U_-#L4&pZMbU0wM#j{R8yRMk*TJRF~2Ny znG!_1y?@gU_KqkYERsdXbb^gh7{+a-?exvM zU%%iO3?YUkAsOTY?p`RDu;EtS*!PcNVlKyT=_7h(;a4^~)&!Rdw-9nuezo?ZHQl(Y z=hd%k-*kWTs3G29mxG$NM60{pG>D>jfo`6#cXW^KogH2-YI?*X*@NRUEw3lK$#eYW zbw6aQ=D0y(*lp=r-~fVGE!@NCE_dr2u#yJ_M#VOcSh&;S`Kz$l#mW-lkXoOGDAy$= z2HYM?uM_YU6_AlEf(bH~O}Zzh;|F-fd4&lXbH}nA)b(I$ex2z-N|uGrL$#5!WLTDi z3d8ItIQKcmmu;A|Ln)^QEWD|z1rN}koCqv6tam-PgQG4RFD6g^H-bEMp<x%2)(eIZ@)XA@NRT*-1^CywKNiUTxDLV>s2;Y-x}4dX_|MS$Uf9Qre4cI*-m*sHNNb^MNCkr{^5~1 zH+&6R)dHoQ7Oq!+1Y@fl)qz#sJen2pbBAJ3jVwOfvWo0+B{wp=bRp1cA8w%no~3H_ zTG54pP)~=00Ev@qYxE(>;63$>nZbJ}P_?nk`(t`u3_S{`Zxb8!7L;yJVN*lPg35s$FYTHG63;^X%U*Wf* zYf8g50oPQNv7zr9t`D3A0L0F(vN3Daf$yQIa7MQj;MUT%a=dXxW!&#o{)v_U<0U9c zK~eE-vNX`0H!||_ido6v1v5F|NMZtIF)8lFWh*JbC-p1FO;{3|WB;4S+OfUgfW9bW z$D%!-X|_|nqS)zcqd zzyV;-*PUZ1Dd5RZ{|`&IR!pv40rMv&L3|^*l2v>cJZYh3x;V8pz%NiJN^S9d=O3Jg zqS&<~3BSocsK2xh&}Sx4=ITHn3~m3nz1eNx8xm5!T9v|pFVXxH2mYHSO0;=&+zysi zrE_icQs|4~;o9)N>}{6LcW@Z}fCQz>;ZnbZ8pw~A&D6at?SMUSF}};)#&5o4?%Sl& zaQbJszPCb>-@7VbJNVgcCrm<1rhPf4oLc$xavjI-Mvpls#bM58eN))n*0^KaWku^%nCocYfRzvo6?R`jLP!UIObiY(egPF*@K-6WT<1;1b5Fiv)WPBAKsKA&e~|& z>T--jxQhca()tP@l7}FYIq&#%=R*kjXG)0J^77G{7(4k@ln>{^878h_S9Ed ziIePjbyNFdNw*7Ex~I0N#KBf;*9=1xBojDL9ki5uCm$g??c$52n*CDh=1|2SA7qB+ z)-r1l2Or(%Cy#ZM7yyOD-GocAcDQ`XGzewj6eRF6yM`;n%{x;oJN2oFo{78&*am}% z@?CEP)$wxRE;%Z>j$J?hGz$9EtJmm)o-W{5%U?uD+nhax9Sdi;SL{K4d%43%ftn}} zFik$?Xp{#&UjCy5U)ywuRBn{sK^g@n;QL4>h(fDo14ai{u~;KbjfW zOLG*|+$y-YHUNgK`?a~2v-~IexQMIgu5B@rLE9ZNdi!Qu+veqWos8kR6n2B(dyPRU zANpyDhnZRN7O+tm`H_ojnbhqIg^#6dt2Bu*;)+WW2}EB$$-mA(Qt~d}Bhy!G1O;=k z^d(ndrushNw+w+L_2kX2^knMB>6+>T=hn_88!(U}sYPWx2UT zFE-LNrm;@pab`7ao)oTK!l*LRZK9ffXVSbLgUEtF%If&v1E-x*;e;BrihgFOKp{Dx zt_c6sx@kS;;c%;T1{^HNx;uo_`NvMhj5={Omle*@>D~KT$raApYybR!!MXr(jL`&Ri$lPI#W1g>p?c|H(yNCO#|e=j0`AqA50jvYI`us4^>{W8C^ z&9DA}_7;>QR_XqVr7UP(>no}Mh35Y#Z~uv|O;}@7M~lyFU5y&_VebY2oxKJunp4;+ zqicTUE075KI@`a5TvD(dX!voTLHiPzJ&Ng9slS=WFWj7(n))Sn3kwmyf$-%pYQF^6 z{3<`hmMjHayTqDib2vzIv|xt*h>r8Elmhhq>9|E$SV=}-Z*Olu-RXq_DCR`%i)G9u zU-8buoPfb#-dzs|Iso9bB@8`0KoWDrV?rK-mo3PO=J&hM%}Ivf1KKTPvAF#~E$3qe zF@cf*zPDRln&&IfI%NR2gzCt|mg$+P%f4pddxngR%#wg(5i$E-Eqn+gOoN4omGHwA zrGSE`xXSvg_DD2xxM83!zkOiG^J9ae$5>MEL}%k|#o@3)f0#3-StR6jnG1&Gb;)jH zuhU%kyunhBRzf9Xt97;iU`5Mz@NqMI+Bn`gp6a_O7o9IL7Pr#+^aG`3 zh_r#JDT{296=BMWN)3Hk=EZTEWmF&!Ep6@!koP8aSg@9*9Q*VD zx;h%B0Kk-Zgy&g~9Z5Z@t@@*h31zCS4pjDB{B@+9aJg@A9{}FJX5W9pe5AAfrDKT# zfYVKX52iNH5~rg@Zg0)U-j6(;z5Ske!2oS97z~MW_4KwiXE}1=xudIb-^|~3IRgzbMef9J$BICoJbf7G{@bufj_5JDTJliXu6H9S2G>M z40?^EDqJy0ve!Fqu4(<{nnp@lVG5hrFa2|+c+>J$Y5Uygx9oOv6kVKqvQV?*Bxw}= zV7S-lwS`|#mf~FZQdXPG>`Hs@9n#9DMdZhyk>t$Vo)h)KZf*-dqeN7F7LtR`mt9o! znx5`R@+~OOy7M%@jgLk2csmjCT|?W^(J}uTsPt9@Q2{yn(XlNa3?;_Cem&to6*noZ z&SKg0@}-8h7$#fEFFror%6x{F82n*85JVIU=Xj!FAXbuz{PZisz4ARR(I@rX2qXNg zY%-TYy8Q4<FMeA!NFuOOZ8SNRQv-1_hJy= zc2->{i3>*Prw-fgwyNz9JTp01%U)&0z1yLJCLaEY;kOeO5+X!#sp~lE;t6=X(}%FE z0LD^r4x{iiE{8#Fjk~b?B>as$xjuL=UP#GT`Q^DzDJc9BX|=wo)_1x3f??tB`lhI5 zttegmRTqa3VRcWpvFM!-ht>fHOLK=y^mU;KJZjj+Z-i5wDkfVhe(hR_LOT@;g?7`giOKD@CtQ9<%8%F@m=#?k*LX6H z(ra<0*!DC1O#RdO!7jSmVSj>jFeBS9^K5gXYBuZh=2IwvHLT^DcIImMR_sVuA`OZ96((R;0m6zZJqosCTNW`Cng z!yUhM5^3(h81M5dC=VVNR_nJr)f#A%q9#&Fi(Cm|$vPSWiTjl9 zZeK-_8_DER`D*xr{=?|O_uof$+p*MaVqyF*i+(F;5r1^)Wk=$7%gjn**|wgYvyN{L zN-TU+m{bs{2jwO;X+Hj}q;Kj@kl$#QYuN05)DX0=#z3#nKj#m*O`-0H%&ZdY=@YB0 zSi6p{b15+?wU_8&N=+E!9&LwBQQq$6o+S6H4J)Q2g9$mLGAB=MT4FEvhp?cYs0B^8 zPql>w`}Y+2<-YD09tFQF>$M{}#N=9Wz>jUm)oLvPOt=?ni_2_EvIiI{Wj(;yN;>`;c5tKJTOT@O$|GgemR)3=u zq#z>)37Hj4q;%DhRm3u9Q7bGvKgcN3O6BP*UOMBsOI!G>PeD5F0n9ggrE`K5$bp}p zPWgbB-`>|N4BoI*earM=e6iK~P2FixmDz9a9ibnPgbRkq`kmRW!&7G`f*7Ml=v{Q0 zTHU5eXPbykDrQJKO`7ost+ul?Ac9`+Xq&7!qx$g*{xKr@v=dQ(R8cb16sdqw>F zR%E?&X{H(;FW=!evOLvBevXt+h`0hTzgSzlFQ_lrjms8D33|g;RMh+^_Ituwn%T|F zonJaiJ0|k<4ADGrKaPWvlPO$zfEw5f{DT_cqXkZux-@Hxde(0(qdX7CF3gSh=-2<= ztU-uympg1a-DVLjgwb5xJv<&dnaP=s2vc>t%Q}%qRjn3JfegsJc9bNrh;G%i(!|2w z7``B~h<1kkkHUdKSSIG20*skx#{gI^7%)Ehe+snnu`Ml{xO0HL>rwr!t6NG@PwMWG zdZ${4mt9VyM`&F*b)JnIA*Afz2UbD4#9eV%@NK!p%9Gi=nOaX+($% z{Z2$j&~{PMHQ!Y`gSJ(pkoS=&tqu*LLgGT48v+jtdPTIqd4+b@d;X0Dja3E#Lp_`; za3H6v*&~LO*$|Dp+7PSDXOw@39_>Cwq%)@(B8E08p#<|9B|P(Djs}#0*1^-CPIt)g zrYkn**vwLUOWyj=@b|;Ovt+H`Cur5u-wn5}+&a1TIGyMi6F0h4z1jmJ_kSvu=Yf~R?(79=LIsg8DQ+b2-BYQsWUgV1_CXh-6#Ffre_xZ6 zgO$;rs=ajRTY%L}|1#d(jzrEy5^*^~V@}eAW2wu}!9`E+mWoB;SIVzHe3`8u1}_Ve z(mHX_z3CQ7TbL?&I91NRUgHXOS1amPr)ON*y|7WAq>KLzd< zi6Sumw@VleV>zA)nMxRsi*jpWB)1~tmWMaNwWv~CJH-)?*S^bLC7=5I(s9`Oc!@n9 zf2`&3xoknoACQBmN23YfhMMsYB_k+BJ#_q68nJFC$pGxP-fSUeo>ZfJTViV(lzZu| zl{&UQh|M4wq(R_)3hOIXn>{*ecafzFgoDEOQ;zFA^P?h79#{%#qm=-azBfdNnKecG zGINK;;x-76AqUzPGq?zPW#jizPOcLBrRt}SGHlb&%y!B);9&831%Jz}w*kdrzO>FR z&Y!lSE3SsnMy&E31mWGM6g+pheXDWz5E~+Z|B4rN{P(U>8Ffz86@mFEex76m+=c1j zhgOgZNpkCN&Wi>;>?#?yxcaWR3_tU(^cSUkig(-3j3T_TUYEAesPq;z(wcb#Oo07N z=i1do(PVj=ZoGCcpB*icCv!^#$_(a2$@I(M?GUV%cU&-w55(Mj z&<(o|VVaMP#KVfb16m=hSD)RVT20(-bkw;inlv6W((juww4i^|4)%n84>h?)%82*% zLD~kLQ-GcJHY_|WaKnK&7FpVI?%@AmM^GG6rE2GE6D3%!; z+)i%vO*h1z3RMjy3g(U9b_e=6UCWueO+nkoSb)G&h%K%?!FR*GKwKDcSEmx3#oTwv zl^sZaP6X}={J#d2TB5THzmbToRrc6a%5mV~mWp*(W&}3aF`u8M@{fLw z>8h=<8Q9S67>#{Ig6*s@$~BdqK9lO^XbDSbQ}LfpTpXeiN^aVAM2LhG)&Fo>SL*)!PJq8!ui4A!M+HU;LAX>3M>kTtsqcoZ{# zsB>y+>TU&iE#9T1zf;Hik#C$dVd*EuQkQqNSS*685w==2UE@-+;&cjVYIc0BLPh`m zFzs6jmZheV@LUQi?a7X4ES;{V3VRv;R9SJ5B649*Ln?@ zfRlQw=&NUdo5p>7!1dz@rcl{`0=N-mGC2SP01x!@u~EW-?I330ycIV>Wxf5f?G4~d zU)bM?9ZeJt`hEZ3eb?v57p8y1tA}-_KxiKGKaKWJZWL(@Wk=~3pOKbY?a_CJoR-eE z{ie1!$P@}n8z;ys)%siECn+yrp9ZO$b!w5xDZg^B0VPE@5E$>`*aJ7v6J#r{;;l*- zyMB4ivoNnvP-FfV8~Hu^g`&b`0jEoi zWdoj$s1F7>_1*2xQpYxka8(OpqCAvlKPFo>M74s06zcGefTxFvK}Gj=KDbvq)ik^M z`WPE!&8e-`%2t~FA&}FGhl!i6gvnD<;dMbC3Qc3`wa2{b@3m7kRswh@=k=hzH<8j1 z)0U2myBKQGT$9Y7v2;qRA<7xE%zNBePmb1^D%aD^x@BhmF4emndv^6pJBQItmjuox zKV*FAtgtX$=P{C#H;9lCFJA7v>(e_XPO}+04^YJ!{gBxHb2dB(^N}kTBZpquR0u4$ z9t0LF`a!R%kdk|PtmF8^FRb>wdj^_dst^(EhX|>dr_c+rlcJQ8ww#|-cJsGQ`fQ*9 zEHU4rG}pB+Sf7_-ytu+~)aiUQ8}LKEWgOPfFnNnUe|dKt_=Qk_9-K0~>sIT+Z|fH7 z^Ys%I0-3)Em5=3qo@uqTpaVtV5~GHU<5u*3ce5;JK8JUiZ4ewJ`?84D>2@5re5>?M zi}gHhb-AK%Go}mg>xcdj*Ja8mS<~C0y`)&nx^nL() zy6Z7(ShE(WAmg<1HFN!^8Imm&a9s zP*Dvhc~wJ$BO&EFh?45GF-taW&R_>3xh!1_#;#?2+FdATN9ZQXrzwLCmuad# zN)zB?UotL>>3QEG=npzkEw4ooD?cqr1h`%l5*j<=aE>UOo@^zIglKrh5ktnq!!tEC mb>TMhoP2a{MVW%fAG8EXNt>Xi(tO0Q0lJ!o8kOqyVgCYTjGpZP literal 0 HcmV?d00001 diff --git a/assets/default-standard-icons.png b/assets/default-standard-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..73e96681ff7917786ad8bce7754c2ac7ed15cf7d GIT binary patch literal 37262 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 QN4{P`MnyV9((vK`0^nI%rvLx| literal 0 HcmV?d00001 diff --git a/assets/gallery.md b/assets/gallery.md index 0dac19b..b317cfd 100644 --- a/assets/gallery.md +++ b/assets/gallery.md @@ -1,13 +1,17 @@ -# Dark Style Sheets +# Gallery -## Breeze Dark +A gallery of various themes and images of the widgets. + +## Dark Style Sheets + +### Breeze Dark

Linux

- Breeze Dark theme for Linux
@@ -15,19 +19,19 @@
Breeze Dark theme for Windows
-## Breeze Dark Green +### Breeze Dark Green

Linux

- Breeze Dark-Green theme for Linux
@@ -35,19 +39,19 @@
Breeze Dark-Green theme for Windows
-## Breeze Dark Purple +### Breeze Dark Purple

Linux

- Breeze Dark-Purple theme for Linux
@@ -55,21 +59,21 @@
Breeze Dark-Purple theme for Windows
-# Light Style Sheets +## Light Style Sheets -## Breeze Light +### Breeze Light

Linux

- Breeze Light theme for Linux
@@ -77,19 +81,19 @@
Breeze Light theme for Windows
-## Breeze Light Green +### Breeze Light Green

Linux

- Breeze Light-Green theme for Linux
@@ -97,19 +101,19 @@
Breeze Light-Green theme for Windows
-## Breeze Light Purple +### Breeze Light Purple

Linux

- Breeze Light-Purple theme for Linux
@@ -117,7 +121,7 @@
Breeze Light-Purple theme for Windows
diff --git a/assets/mismatched_titlebar.png b/assets/mismatched_titlebar.png new file mode 100644 index 0000000000000000000000000000000000000000..193f7790cc89a452c8b0c2e4e5cc0aa6058954ef GIT binary patch literal 5940 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 system_theme.Theme: + '''Detect the system theme.''' + + if QT_VERSION >= (6, 5, 0): + color_scheme = app.styleHints().colorScheme() + if color_schema == ColorScheme.Unknown: + return system_theme.UNKNOWN + elif color_schema == ColorScheme.Light: + return system_theme.LIGHT + return system_theme.DARK + return system_theme.get_theme() +``` + +## System Icons + +To provide consistent themes, you can override the default system icons. An [example](/example/icons/standard.py) comparing the icons between Windows and our overrides is below. This requires configuring with the `standard-icons` extension. + + + + + + + + + + + + + + +
Default Icons
Custom Icons
The dark stylesheet demo.The light stylesheet demo.
+ +Then, create a style factory and register the style with your style: + +```python +# where the style name is a Qt style, like Windows or Fusion, +style = QtWidgets.QStyleFactory.create('Windows') +style = StandardIconStyle(style) +app.setStyle(style) +``` + +## Custom Titlebars + +When the stylesheet color does not match the system theme, the titlebar can look out of place with the application. + +![Mismatched Titlebar](/assets/mismatched_titlebar.png) + +Removing the application titlebar and using a custom [titlebar](/example/titlebar/titlebar.py) can create a consistent look anbd feel. + +First, ensure the main window (and any subwindows) remove the titlebar, optionally removing hints: + +```python +# this removes the title bar, and optionally the help and shade button hints +flags = titlebar.QtCore.Qt.WindowType(0) +flags |= titlebar.compat.WindowContextHelpButtonHint +flags |= titlebar.compat.WindowShadeButtonHint +``` + +Next, initialize the application, load your stylesheet, and install an event filter for the window to track move, drag, and other events: + +```python +app = QtWidgets.QApplication.instance() +app.installEventFilter(window) +``` + +This custom titlebar supports the following: +- Title text +- Title bar with menu, help, min, max, restore, close, shade, and unshade. + - Help, shade, and unshade are optional. + - Menu contains restore, min, max, move, resize, stay on top, and close. +- Custom window minimization. + - Minimized windows can be placed in any corner. + - Windows reposition on resize events to avoid truncating windows. +- Dynamically toggle window state to keep windows above others. +- Drag titlebar to move window +- Double click titlebar to change window state. + - Restores if maximized or minimized. + - Shades or unshades if in normal state and applicable. + - Otherwise, maximizes window. +- Context menu move and resize events. + - Click "Size" to resize from the bottom right based on cursor. + - Click "Move" to move bottom-center of titlebar to cursor. +- Drag to resize on window border with or without size grips. + - If the window contains size grips, use the default behavior. + - Otherwise, monitor mouse and hover events on window border. + - If hovering over window border, draw appropriate resize cursor. + - If clicked on window border, enter resize mode. + - Click again to exit resize mode. +- Custom border width for a window outline. + +The following Qt properties ensure proper styling of the UI: +- `isTitlebar`: should be set on the title bar. ensures all widgets in the title bar have the correct background. +- `isWindow`: set on the window to ensure there is no default border. +- `hasWindowFrame`: set on a window with a border to draw the frame. + +![Custom Titlebar](/assets/custom_titlebar.png) + +The custom titlebar mas **MAJOR** limitations and if possible, it's better to change the look and feel using your OS's API. + +- **Linux - Wayland** + - Cannot move the window position. This cannot be done even if you know the compositor (such as kwin). + - Cannot use the menu resize due to `QWidget::mouseGrab()`. + - This plugin supports grabbing the mouse only for popup windows + - The window stops tracking mouse movements past a certain distance. + - Attempting to move the window position causes global position to be wrong. + - Wayland does not support `Stay on Top` directive. + - qt.qpa.wayland: Wayland does not support QWindow::requestActivate() + - The menu resize has to guess the mouse position outside of the window bounds. + - This cannot be fixed since we cannot use mouse events if the user is outside the main window, nor do hover events trigger. We cannot guess where the user left the main window, since `QCursor::pos` will not be updated until the user moves the mouse within the application, so merely resizing until the actual cursor is within the window won't work. + - We cannot intercept mouse events for the menu resize outside the window (this even occurs when forcing X11 on Wayland). +- **Windows** + - Cannot resize the menu. + - Subwindows and windows cannot track outside the main window boundaries. + +**Customizing Windows Title Bars via the Win32 API:** + +On Windows, you can use the [Desktop Window Manager](https://learn.microsoft.com/en-us/windows/win32/api/_dwm/) Win32 API on Windows 10+. + +First, get the HDNL for the [window](https://doc.qt.io/qt-6/qwidget.html#winId). + +```cpp +auto window_handle = reinterpret_cast(window.winId()); +``` + +To toggle dark mode on or off, use: + +```cpp +#include +#include + +// set to true or false +auto use_dark_mode = true; +auto success = SUCCEEDED(DwmSetWindowAttribute( + window_handle, + DWMWINDOWATTRIBUTE::DWMWA_USE_IMMERSIVE_DARK_MODE, + &use_dark_mode, + sizeof(use_dark_mode))); +``` + +You can set specific [colors](https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute) as well to mimic the stylesheet theme, such as the border and caption colors, and then redraw the window to set the colors. Colors are provided in the `0x00BBGGRR` format. The supported colors are: + +- Border: `DWMWA_BORDER_COLOR` +- Caption: `DWMWA_CAPTION_COLOR` +- Text: `DWMWA_TEXT_COLOR` + +```cpp +#include +#include + +COLORREF color = 0x00505050; +auto success = SUCCEEDED(DwmSetWindowAttribute( + window_handle, + DWMWINDOWATTRIBUTE::DWMWA_BORDER_COLOR, + &color, + sizeof(color))); + +// you must redraw the window to change the titlebar colors +ShowWindow(window_handle, SW_MINIMIZE); +ShowWindow(window_handle, SW_RESTORE); +``` + +## Dial Widgets + +The standard [QDial] widget cannot be stylized via stylesheets and is quite aesthetically unappealing. However, subclassing the paint events in `QDial` can apply the stylesheet colors to restyle the [dial](/example/dial/dial.py). + +![Custom Dial](/assets/custom_dial.png) + +## Branchless QTreeView + +This is an example widget for a `QTreeView` where the branch indicators are hidden. In order to add these branchless indicators to your project, copy the branchless [directory](/example/branchless/) into the [extension](/extension) folder, and then configure with (adding any additional resources or styles as you see fit): + +```bash +# choose the desired framework from pyqt5, pyqt6, pyside2, pyside6 +framework=pyqt5 +python configure.py \ + --styles=all \ + --extensions=all \ + --qt-framework \ + "${framework}" \ + --resource breeze.qrc \ + --compiled-resource \ + "breeze_${framework}.py" +``` + +Then, to remove the branch indicators, you must also set the object name for each `QTreeView` or `QTreeWidget` to `"branchless"`, for example, in Python, `tree.setObjectName("branchless")`. + + + + + + + + + + + + + + +
Dark
Light
+ Breeze Dark theme using branchless indicators for Windows + + Breeze Light theme using branchless indicators for Windows +
+ +## LCD + +Similar to [QDial], the standard [QLCDNumber] widget cannot be stylized via stylesheets and is quite aesthetically unappealing. However, subclassing the paint events in `QLCDNumber` can apply the stylesheet colors to restyle the [LCD display](/example/lcd/lcd.py). + +![Custom LCD](/assets/custom_lcd.png) + +## Slider + +Similar to [QDial], the standard [QSlider] widget cannot be stylized via stylesheets and is quite aesthetically unappealing. However, subclassing the paint events in `QSlider` can apply the stylesheet colors to restyle the [slider](/example/slider/slider.py). + +![Custom Slider](/assets/custom_slider.png) + +## Non-Stylesheet Styling + +Stylesheets have limitations, as does subclassing individual widgets to override paint events. You can provide more general styling by overriding [QCommonStyle]. See [System Icons](#system-icons) for a simple example implementing a general look and feel of the UI. + +First, create a custom subclass of `QCommonStyle`: + +```python +class CustomStyle(QtWidgets.QCommonStyle): + '''A custom application style.''' + + # implementation goes here +``` + +Then, create a style factory and register the style with your style: + +```python +# where the style name is a Qt style, like Windows or Fusion, +style = QtWidgets.QStyleFactory.create('Windows') +style = CustomStyle(style) +app.setStyle(style) +``` + +## CMake + +Using CMake, you can download, configure, and compile the resources as part part of the build process. The following configurations are provided by [ruilvo](https://github.com/ruilvo/). You can see a full example in [example](/example/cmake/). First, use the CMake module [breeze.cmake](/example/cmake/breeze.cmake) and create a [CMakeLists](/example/cmake/CMakeLists.txt). + +Add in cached variables necessary to configure the `breeze.cmake` module: + +```cmake +set(QT_VERSION Qt5 CACHE STRING "The Qt version framework to use (Qt5 or Qt6).") +set(BREEZE_EXTENSIONS all CACHE STRING "The extensions to include in our stylesheets.") +set(BREEZE_STYLES all CACHE STRING "The styles to include in our stylesheets.") +``` + +Then, include the module and link the libraries to our executable: + +```cmake +include(${CMAKE_CURRENT_SOURCE_DIR}/breeze.cmake) +set(SOURCE_FILES ...) +add_executable(testing ${SOURCE_FILES}) +target_link_libraries(executable PRIVATE Qt${QT_VERSION_MAJOR}::Widgets breeze) +``` + +## Example Widgets + +Examples of some simple UIs with our stylesheets include: + +[**Widgets**](/example/widgets.py) + +![Widgets](/assets/Breeze%20Dark.gif) + +[**Placeholder Text**](/example/placeholder_text.py) + +This only works on Qt5. + +![Custom Placeholder Text](/assets/custom_placeholder_text.png) + +[**What's This**](/example/whatsthis.py) + +![What's This](/assets/custom_whatsthis.png) + +[**URL**](/example/url.py) + +![URL](/assets/custom_url.png) + + +[QDial]: https://doc.qt.io/qt-6/qdial.html +[QLCDNumber]: https://doc.qt.io/qt-6/qlcdnumber.html +[QSlider]: https://doc.qt.io/qt-6/qslider.html +[QCommonStyle]: https://doc.qt.io/qt-6/qcommonstyle.html diff --git a/example/branchless/README.md b/example/branchless/README.md deleted file mode 100644 index 2588d37..0000000 --- a/example/branchless/README.md +++ /dev/null @@ -1,31 +0,0 @@ -branchless -========== - -This contains an example widget for a `QTreeView` where the branch indicators are hidden. In order to add these branchless indicators to your project, copy this directory into the [extension](/extension) folder, and then configure with (adding any additional resources or styles as you see fit): - -```bash -python configure.py --extensions=branchless --resource custom.qrc -``` - -To remove the branch indicators, you must also set the object name for each `QTreeView` or `QTreeWidget` to `"branchless"`, for example, in Python, `tree.setObjectName("branchless")`. - -## Example - -

Dark

-
- Breeze Dark theme using branchless indicators for Windows -
- - -

Light

-
- Breeze Light theme using branchless indicators for Windows -
diff --git a/example/branchless/application.py b/example/branchless/main.py similarity index 96% rename from example/branchless/application.py rename to example/branchless/main.py index 94fbeef..1153a8a 100644 --- a/example/branchless/application.py +++ b/example/branchless/main.py @@ -32,8 +32,9 @@ import os import sys -HOME = os.path.dirname(os.path.realpath(__file__)) -sys.path.insert(0, os.path.dirname(HOME)) +EXAMPLE = os.path.dirname(os.path.realpath(__file__)) +HOME = os.path.dirname(EXAMPLE) +sys.path.insert(0, HOME) import shared # noqa # pylint: disable=wrong-import-position,import-error import widgets # noqa # pylint: disable=wrong-import-position,import-error diff --git a/example/cmake/breeze.cmake b/example/cmake/breeze.cmake index 0fa23d7..f3ff6d4 100644 --- a/example/cmake/breeze.cmake +++ b/example/cmake/breeze.cmake @@ -23,6 +23,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/Alexhuszagh/BreezeStyleSheets.git GIT_TAG origin/main GIT_PROGRESS ON + GIT_SHALLOW 1 USES_TERMINAL_DOWNLOAD TRUE) FetchContent_GetProperties(breeze_stylesheets) diff --git a/example/breeze_theme.hpp b/example/detect/system_theme.hpp similarity index 99% rename from example/breeze_theme.hpp rename to example/detect/system_theme.hpp index 04603db..8db8d84 100644 --- a/example/breeze_theme.hpp +++ b/example/detect/system_theme.hpp @@ -1,5 +1,5 @@ /** - * breeze_theme + * system_theme * ============ * * Determine if the system theme is light or dark, supporting many platforms. diff --git a/example/breeze_theme.py b/example/detect/system_theme.py similarity index 99% rename from example/breeze_theme.py rename to example/detect/system_theme.py index 653c41e..a02864a 100644 --- a/example/breeze_theme.py +++ b/example/detect/system_theme.py @@ -1,5 +1,5 @@ ''' - breeze_theme + system_theme ============ Get the current system them information. This is adapted from darkdetect diff --git a/example/dial.py b/example/dial/dial.py similarity index 88% rename from example/dial.py rename to example/dial/dial.py index 3a385cb..1d0fdd3 100644 --- a/example/dial.py +++ b/example/dial/dial.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # The MIT License (MIT) # # Copyright (c) <2022-Present> @@ -32,9 +30,13 @@ ''' import math +import os import sys -import shared +EXAMPLE = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.dirname(EXAMPLE)) + +import shared # noqa # pylint: disable=wrong-import-position,import-error parser = shared.create_parser() parser.add_argument( @@ -270,49 +272,3 @@ def eventFilter(self, obj, event): self.repaint() return super().eventFilter(obj, event) - - -class Ui: - '''Main class for the user interface.''' - - def setup(self, MainWindow): - '''Setup our main window for the UI.''' - - MainWindow.setObjectName('MainWindow') - MainWindow.resize(1068, 824) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName('centralwidget') - self.layout = QtWidgets.QHBoxLayout(self.centralwidget) - self.layout.setObjectName('layout') - if not args.no_align: - self.layout.setAlignment(compat.AlignVCenter) - MainWindow.setCentralWidget(self.centralwidget) - - self.dial1 = Dial(self.centralwidget) - self.layout.addWidget(self.dial1) - - self.dial2 = Dial(self.centralwidget) - self.dial2.setNotchesVisible(True) - self.layout.addWidget(self.dial2) - - self.dial3 = Dial(self.centralwidget) - self.dial3.setWrapping(True) - self.layout.addWidget(self.dial3) - - -def main(): - 'Application entry point' - - app, window = shared.setup_app(args, unknown, compat) - - # setup ui - ui = Ui() - ui.setup(window) - window.setWindowTitle('QDial') - - shared.set_stylesheet(args, app, compat) - return shared.exec_app(args, app, window) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/example/dial/main.py b/example/dial/main.py new file mode 100644 index 0000000..3212c8f --- /dev/null +++ b/example/dial/main.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +''' + dial + ==== + + Sample UI widget with our dial. +''' + +import sys + +import dial + + +class Ui: + '''Main class for the user interface.''' + + def setup(self, MainWindow): + '''Setup our main window for the UI.''' + + MainWindow.setObjectName('MainWindow') + MainWindow.resize(1068, 824) + self.centralwidget = dial.compat.QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName('centralwidget') + self.layout = dial.compat.QtWidgets.QHBoxLayout(self.centralwidget) + self.layout.setObjectName('layout') + if not dial.args.no_align: + self.layout.setAlignment(dial.compat.AlignVCenter) + MainWindow.setCentralWidget(self.centralwidget) + + self.dial1 = dial.Dial(self.centralwidget) + self.layout.addWidget(self.dial1) + + self.dial2 = dial.Dial(self.centralwidget) + self.dial2.setNotchesVisible(True) + self.layout.addWidget(self.dial2) + + self.dial3 = dial.Dial(self.centralwidget) + self.dial3.setWrapping(True) + self.layout.addWidget(self.dial3) + + +def main(): + 'Application entry point' + + app, window = dial.shared.setup_app(dial.args, dial.unknown, dial.compat) + + # setup ui + ui = Ui() + ui.setup(window) + window.setWindowTitle('QDial') + window.resize(400, 150) + + dial.shared.set_stylesheet(dial.args, app, dial.compat) + return dial.shared.exec_app(dial.args, app, window) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/example/standard_icons.py b/example/icons/main.py similarity index 55% rename from example/standard_icons.py rename to example/icons/main.py index b00db10..21d9d8a 100644 --- a/example/standard_icons.py +++ b/example/icons/main.py @@ -1,27 +1,4 @@ #!/usr/bin/env python -# -# The MIT License (MIT) -# -# Copyright (c) <2022-Present> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the 'Software'), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - ''' standard_icons ============== @@ -31,59 +8,7 @@ import sys -import shared - -parser = shared.create_parser() -args, unknown = shared.parse_args(parser) -QtCore, QtGui, QtWidgets = shared.import_qt(args) -compat = shared.get_compat_definitions(args) -ICON_MAP = shared.get_icon_map(compat) - - -def style_icon(style, icon, option=None, widget=None): - '''Helper to provide arguments for setting a style icon.''' - return shared.style_icon(args, style, icon, ICON_MAP, option, widget) - - -class ApplicationStyle(QtWidgets.QCommonStyle): - '''A custom application style overriding standard icons.''' - - def __init__(self, style): - super().__init__() - self.style = style - - def __getattribute__(self, item): - ''' - Override for standardIcon. Everything else should default to the - system default. We cannot have `style_icon` be a member of - `ApplicationStyle`, since this will cause an infinite recursive loop. - ''' - - if item == 'standardIcon': - return lambda *x: style_icon(self, *x) - return getattr(self.style, item) - - -def add_standard_button(ui, layout, icon, index): - '''Create and add a QToolButton with a standard icon.''' - - button = QtWidgets.QToolButton(ui.centralwidget) - setattr(ui, f'button{index}', button) - button.setAutoRaise(True) - button.setIcon(style_icon(button.style(), icon, widget=button)) - button.setObjectName(f'button{index}') - layout.addWidget(button) - - -def add_standard_buttons(ui, page, icons): - '''Create and add QToolButtons with standard icons to the UI.''' - - _ = ui - for icon_name in icons: - icon_enum = getattr(compat, icon_name) - icon = style_icon(page.style(), icon_enum, widget=page) - item = QtWidgets.QListWidgetItem(icon, icon_name) - page.addItem(item) +import standard class Ui: @@ -94,19 +19,19 @@ def setup(self, MainWindow): # pylint: disable=too-many-statements MainWindow.setObjectName('MainWindow') MainWindow.resize(1068, 824) - self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget = standard.compat.QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName('centralwidget') - self.layout = QtWidgets.QVBoxLayout(self.centralwidget) + self.layout = standard.compat.QtWidgets.QVBoxLayout(self.centralwidget) self.layout.setObjectName('layout') - self.layout.setAlignment(compat.AlignHCenter) + self.layout.setAlignment(standard.compat.AlignHCenter) MainWindow.setCentralWidget(self.centralwidget) - self.tool_box = QtWidgets.QToolBox(self.centralwidget) - self.page1 = QtWidgets.QListWidget() + self.tool_box = standard.compat.QtWidgets.QToolBox(self.centralwidget) + self.page1 = standard.compat.QtWidgets.QListWidget() self.tool_box.addItem(self.page1, 'Overwritten Icons') self.layout.addWidget(self.tool_box) - add_standard_buttons( + standard.add_standard_buttons( self, self.page1, [ @@ -127,7 +52,7 @@ def setup(self, MainWindow): # pylint: disable=too-many-statements ], ) - self.page2 = QtWidgets.QListWidget() + self.page2 = standard.QtWidgets.QListWidget() self.tool_box.addItem(self.page2, 'Default Icons') self.layout.addWidget(self.tool_box) @@ -197,71 +122,71 @@ def setup(self, MainWindow): # pylint: disable=too-many-statements 'SP_DialogIgnoreButton', 'SP_RestoreDefaultsButton', ] - if compat.QT_VERSION >= (6, 3, 0): + if standard.compat.QT_VERSION >= (6, 3, 0): default_icons.append('SP_TabCloseButton') - add_standard_buttons(self, self.page2, default_icons) + standard.add_standard_buttons(self, self.page2, default_icons) - self.dockWidget1 = QtWidgets.QDockWidget(MainWindow) + self.dockWidget1 = standard.compat.QtWidgets.QDockWidget(MainWindow) self.dockWidget1.setObjectName('dockWidget1') - self.dockWidgetContents = QtWidgets.QWidget() + self.dockWidgetContents = standard.compat.QtWidgets.QWidget() self.dockWidgetContents.setObjectName('dockWidgetContents') self.dockWidget1.setWidget(self.dockWidgetContents) - MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(1), self.dockWidget1) + MainWindow.addDockWidget(standard.compat.QtCore.Qt.DockWidgetArea(1), self.dockWidget1) - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.dockWidgetContents) + self.verticalLayout_2 = standard.compat.QtWidgets.QVBoxLayout(self.dockWidgetContents) self.verticalLayout_2.setObjectName('verticalLayout_2') - self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout = standard.compat.QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName('verticalLayout') - self.comboBox = QtWidgets.QComboBox(self.dockWidgetContents) + self.comboBox = standard.compat.QtWidgets.QComboBox(self.dockWidgetContents) self.comboBox.setObjectName('comboBox') self.comboBox.setEditable(True) self.comboBox.addItem('First') self.comboBox.addItem('Second') self.verticalLayout.addWidget(self.comboBox) - self.horizontalSlider = QtWidgets.QSlider(self.dockWidgetContents) - self.horizontalSlider.setOrientation(compat.Horizontal) + self.horizontalSlider = standard.compat.QtWidgets.QSlider(self.dockWidgetContents) + self.horizontalSlider.setOrientation(standard.compat.Horizontal) self.horizontalSlider.setObjectName('horizontalSlider') self.verticalLayout.addWidget(self.horizontalSlider) - self.textEdit = QtWidgets.QTextEdit(self.dockWidgetContents) + self.textEdit = standard.compat.QtWidgets.QTextEdit(self.dockWidgetContents) self.textEdit.setObjectName('textEdit') self.verticalLayout.addWidget(self.textEdit) - self.line = QtWidgets.QFrame(self.dockWidgetContents) - self.line.setFrameShape(compat.HLine) - self.line.setFrameShadow(compat.Sunken) + self.line = standard.compat.QtWidgets.QFrame(self.dockWidgetContents) + self.line.setFrameShape(standard.compat.HLine) + self.line.setFrameShadow(standard.compat.Sunken) self.line.setObjectName('line') self.verticalLayout.addWidget(self.line) - self.progressBar = QtWidgets.QProgressBar(self.dockWidgetContents) + self.progressBar = standard.compat.QtWidgets.QProgressBar(self.dockWidgetContents) self.progressBar.setProperty('value', 24) self.progressBar.setObjectName('progressBar') self.verticalLayout.addWidget(self.progressBar) self.verticalLayout_2.addLayout(self.verticalLayout) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1068, 29)) + self.menubar = standard.compat.QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(standard.compat.QtCore.QRect(0, 0, 1068, 29)) self.menubar.setObjectName('menubar') - self.menuMenu = QtWidgets.QMenu(self.menubar) + self.menuMenu = standard.compat.QtWidgets.QMenu(self.menubar) self.menuMenu.setObjectName('menuMenu') MainWindow.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar = standard.compat.QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName('statusbar') MainWindow.setStatusBar(self.statusbar) - self.actionAction = compat.QAction(MainWindow) + self.actionAction = standard.compat.QAction(MainWindow) self.actionAction.setObjectName('actionAction') - self.actionAction_C = compat.QAction(MainWindow) + self.actionAction_C = standard.compat.QAction(MainWindow) self.actionAction_C.setObjectName('actionAction_C') self.menuMenu.addAction(self.actionAction) self.menuMenu.addAction(self.actionAction_C) self.menubar.addAction(self.menuMenu.menuAction()) - QtCore.QMetaObject.connectSlotsByName(MainWindow) + standard.compat.QtCore.QMetaObject.connectSlotsByName(MainWindow) self.retranslateUi(MainWindow) def retranslateUi(self, MainWindow): '''Retranslate our UI after initializing some of our base modules.''' - _translate = QtCore.QCoreApplication.translate + _translate = standard.compat.QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate('MainWindow', 'MainWindow')) self.menuMenu.setTitle(_translate('MainWindow', '&Menu')) self.actionAction.setText(_translate('MainWindow', '&Action')) @@ -269,17 +194,19 @@ def retranslateUi(self, MainWindow): def about(self): '''Load our Qt about window.''' - QtWidgets.QMessageBox.aboutQt(self.centralwidget, 'About Menu') + standard.compat.QtWidgets.QMessageBox.aboutQt(self.centralwidget, 'About Menu') def critical(self): '''Launch a critical message box.''' - QtWidgets.QMessageBox.critical(self.centralwidget, 'Error', 'Critical Error') + standard.compat.QtWidgets.QMessageBox.critical(self.centralwidget, 'Error', 'Critical Error') def main(): 'Application entry point' - app, window = shared.setup_app(args, unknown, compat, style_class=ApplicationStyle) + app, window = standard.shared.setup_app( + standard.args, standard.unknown, standard.compat, style_class=standard.StandardIconStyle + ) # setup ui ui = Ui() @@ -290,8 +217,8 @@ def main(): ui.actionAction.triggered.connect(ui.about) ui.actionAction_C.triggered.connect(ui.critical) - shared.set_stylesheet(args, app, compat) - return shared.exec_app(args, app, window) + standard.shared.set_stylesheet(standard.args, app, standard.compat) + return standard.shared.exec_app(standard.args, app, window) if __name__ == '__main__': diff --git a/example/icons/standard.py b/example/icons/standard.py new file mode 100644 index 0000000..b33120b --- /dev/null +++ b/example/icons/standard.py @@ -0,0 +1,87 @@ +# The MIT License (MIT) +# +# Copyright (c) <2022-Present> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +''' + standard_icons + ============== + + Example overriding QCommonStyle for custom standard icons. +''' + +import os +import sys + +HOME = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.dirname(HOME)) + +import shared # noqa # pylint: disable=wrong-import-position,import-error + +parser = shared.create_parser() +args, unknown = shared.parse_args(parser) +QtCore, QtGui, QtWidgets = shared.import_qt(args) +compat = shared.get_compat_definitions(args) +ICON_MAP = shared.get_icon_map(compat) + + +def style_icon(style, icon, option=None, widget=None): + '''Helper to provide arguments for setting a style icon.''' + return shared.style_icon(args, style, icon, ICON_MAP, option, widget) + + +class StandardIconStyle(QtWidgets.QCommonStyle): + '''A custom application style overriding standard icons.''' + + def __init__(self, style): + super().__init__() + self.style = style + + def __getattribute__(self, item): + ''' + Override for standardIcon. Everything else should default to the + system default. We cannot have `style_icon` be a member of + `StandardIconStyle`, since this will cause an infinite recursive loop. + ''' + if item == 'standardIcon': + return lambda *x: style_icon(self, *x) + return getattr(object.__getattribute__(self, 'style'), item) + + +def add_standard_button(ui, layout, icon, index): + '''Create and add a QToolButton with a standard icon.''' + + button = QtWidgets.QToolButton(ui.centralwidget) + setattr(ui, f'button{index}', button) + button.setAutoRaise(True) + button.setIcon(style_icon(button.style(), icon, widget=button)) + button.setObjectName(f'button{index}') + layout.addWidget(button) + + +def add_standard_buttons(ui, page, icons): + '''Create and add QToolButtons with standard icons to the UI.''' + + _ = ui + for icon_name in icons: + icon_enum = getattr(compat, icon_name) + icon = style_icon(page.style(), icon_enum, widget=page) + item = QtWidgets.QListWidgetItem(icon, icon_name) + page.addItem(item) diff --git a/example/lcd.py b/example/lcd/lcd.py similarity index 54% rename from example/lcd.py rename to example/lcd/lcd.py index 98a2ad7..c0e07ab 100644 --- a/example/lcd.py +++ b/example/lcd/lcd.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # The MIT License (MIT) # # Copyright (c) <2022-Present> @@ -23,24 +21,29 @@ # THE SOFTWARE. ''' - dial - ==== + lcd + === Example showing how to override the `paintEvent` and `eventFilter` - for a `QDial`, creating a visually consistent, stylish `QDial` that - supports highlighting the handle on the active or hovered dial. + for a `QLCDNumber`, creating a visually consistent, stylish + `QLCDNumber` that supports highlighting the handle on the active + or hovered number. ''' +import os import sys -import shared +EXAMPLE = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.dirname(EXAMPLE)) + +import shared # noqa # pylint: disable=wrong-import-position,import-error parser = shared.create_parser() parser.add_argument( '--no-align', help='''allow larger widgets without forcing alignment.''', action='store_true' ) args, unknown = shared.parse_args(parser) -QtCore, QtGui, QtWidgets = shared.import_qt(args) +_, __, QtWidgets = shared.import_qt(args) compat = shared.get_compat_definitions(args) colors = shared.get_colors(args, compat) @@ -65,62 +68,3 @@ def __init__(self, widget=None): palette.setColor(compat.LightPalette, colors.Selected) palette.setColor(compat.DarkPalette, colors.Notch) self.setPalette(palette) - - -class Ui: - '''Main class for the user interface.''' - - def setup(self, MainWindow): - '''Setup our main window for the UI.''' - - MainWindow.setObjectName('MainWindow') - MainWindow.resize(1068, 824) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.layout = QtWidgets.QHBoxLayout(self.centralwidget) - self.layout.setSpacing(0) - if not args.no_align: - self.layout.setAlignment(compat.AlignVCenter) - MainWindow.setCentralWidget(self.centralwidget) - - self.lcd1 = LCD(self.centralwidget) - self.lcd1.display(15) - self.lcd1.setDigitCount(2) - self.layout.addWidget(self.lcd1) - - self.lcd2 = LCD(self.centralwidget) - self.lcd2.display(31) - self.lcd2.setHexMode() - self.lcd2.setDigitCount(2) - self.layout.addWidget(self.lcd2) - - self.lcd3 = LCD(self.centralwidget) - self.lcd3.display(15) - self.lcd3.setSegmentStyle(compat.LCDOutline) - self.lcd3.setFrameShape(compat.NoFrame) - self.lcd3.setDigitCount(2) - self.layout.addWidget(self.lcd3) - - self.lcd4 = LCD(self.centralwidget) - self.lcd4.display(15) - self.lcd4.setSegmentStyle(compat.LCDFlat) - self.lcd4.setFrameShape(compat.NoFrame) - self.lcd4.setDigitCount(2) - self.layout.addWidget(self.lcd4) - - -def main(): - 'Application entry point' - - app, window = shared.setup_app(args, unknown, compat) - - # setup ui - ui = Ui() - ui.setup(window) - window.setWindowTitle('QLCDNumber') - - shared.set_stylesheet(args, app, compat) - return shared.exec_app(args, app, window) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/example/lcd/main.py b/example/lcd/main.py new file mode 100644 index 0000000..a73e29d --- /dev/null +++ b/example/lcd/main.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +''' + lcd + === + + Example showing how to override the `paintEvent` and `eventFilter` + for a `QLCDNumber`, creating a visually consistent, stylish + `QLCDNumber` that supports highlighting the handle on the active + or hovered number. +''' + +import sys + +import lcd + + +class Ui: + '''Main class for the user interface.''' + + def setup(self, MainWindow): + '''Setup our main window for the UI.''' + + MainWindow.setObjectName('MainWindow') + MainWindow.resize(1068, 824) + self.centralwidget = lcd.QtWidgets.QWidget(MainWindow) + self.layout = lcd.QtWidgets.QHBoxLayout(self.centralwidget) + self.layout.setSpacing(0) + if not lcd.args.no_align: + self.layout.setAlignment(lcd.compat.AlignVCenter) + MainWindow.setCentralWidget(self.centralwidget) + + self.lcd1 = lcd.LCD(self.centralwidget) + self.lcd1.display(15) + self.lcd1.setDigitCount(2) + self.layout.addWidget(self.lcd1) + + self.lcd2 = lcd.LCD(self.centralwidget) + self.lcd2.display(31) + self.lcd2.setHexMode() + self.lcd2.setDigitCount(2) + self.layout.addWidget(self.lcd2) + + self.lcd3 = lcd.LCD(self.centralwidget) + self.lcd3.display(15) + self.lcd3.setSegmentStyle(lcd.compat.LCDOutline) + self.lcd3.setFrameShape(lcd.compat.NoFrame) + self.lcd3.setDigitCount(2) + self.layout.addWidget(self.lcd3) + + self.lcd4 = lcd.LCD(self.centralwidget) + self.lcd4.display(15) + self.lcd4.setSegmentStyle(lcd.compat.LCDFlat) + self.lcd4.setFrameShape(lcd.compat.NoFrame) + self.lcd4.setDigitCount(2) + self.layout.addWidget(self.lcd4) + + +def main(): + 'Application entry point' + + app, window = lcd.shared.setup_app(lcd.args, lcd.unknown, lcd.compat) + + # setup ui + ui = Ui() + ui.setup(window) + window.setWindowTitle('QLCDNumber') + window.resize(400, 150) + + lcd.shared.set_stylesheet(lcd.args, app, lcd.compat) + return lcd.shared.exec_app(lcd.args, app, window) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/example/placeholder_text.py b/example/placeholder_text.py index a1cec31..fb3210b 100644 --- a/example/placeholder_text.py +++ b/example/placeholder_text.py @@ -114,6 +114,7 @@ def main(): ui = Ui() ui.setup(window) window.setWindowTitle('Stylized Placeholder Text.') + window.resize(400, 150) shared.set_stylesheet(args, app, compat) return shared.exec_app(args, app, window) diff --git a/example/shared.py b/example/shared.py index c6c2324..0fa0987 100644 --- a/example/shared.py +++ b/example/shared.py @@ -13,12 +13,13 @@ import os import sys -import breeze_theme - example_dir = os.path.dirname(os.path.realpath(__file__)) home = os.path.dirname(example_dir) dist = os.path.join(home, 'dist') -IS_DARK = None +sys.path.append(home) +THEME = None + +from example.detect import system_theme # noqa # pylint: disable=wrong-import-position,import-error def create_parser(): @@ -81,12 +82,13 @@ def parse_args(parser): def normalize_stylesheet(stylesheet): '''Normalize the stylesheet, removing and normalizing any aliases.''' - # now we need to normalize our theme + # now we need to normalize our theme. we don't use Qt6 features + # so we can differentiat between light/dark/unknown. if stylesheet.startswith('auto'): - theme = breeze_theme.get_theme() - if theme == breeze_theme.Theme.DARK: + theme = system_theme.get_theme() + if theme == system_theme.Theme.DARK: stylesheet = stylesheet.replace('auto', 'dark', 1) - elif theme == breeze_theme.Theme.LIGHT: + elif theme == system_theme.Theme.LIGHT: stylesheet = stylesheet.replace('auto', 'light', 1) else: logging.warning('Unknown an unknown system theme, falling back to the system native theme.') @@ -956,7 +958,7 @@ def setup_app(args, unknown, compat, style_class=None, window_class=None): if app is None: app = compat.QtWidgets.QApplication(sys.argv[:1] + unknown) # NOTE: Need to detect if the style is dark mode here - _ = is_dark_mode(compat) + _ = get_theme(compat) if args.style != 'native': style = compat.QtWidgets.QStyleFactory.create(args.style) if style_class is not None: @@ -980,13 +982,13 @@ def setup_app(args, unknown, compat, style_class=None, window_class=None): return app, window -def is_dark_mode(compat, reinitialize=False): +def get_theme(compat, reinitialize=False): '''Determine if the system theme is in dark mode.''' - global IS_DARK + global THEME - if IS_DARK is not None and not reinitialize: - return IS_DARK + if THEME is not None and not reinitialize: + return THEME app = compat.QtWidgets.QApplication.instance() if app is None: @@ -994,15 +996,17 @@ def is_dark_mode(compat, reinitialize=False): if compat.QT_VERSION >= (6, 5, 0): color_scheme = app.styleHints().colorScheme() - IS_DARK = color_scheme == color_scheme.__class__.Dark + theme_cls = color_scheme.__class__ + if color_scheme == theme_cls.Unknown: + THEME = system_theme.Theme.UNKNOWN + elif color_scheme == theme_cls.Light: + THEME = system_theme.Theme.LIGHT + else: + THEME = system_theme.Theme.DARK else: - # NOTE: This does not work, it only gives the default app color - # which on early versions of Qt defaults to the application style. - text = app.palette().windowText().color() - window = app.palette().window().color() - IS_DARK = window.lightness() > text.lightness() + THEME = system_theme.get_theme() - return IS_DARK + return THEME def read_qtext_file(path, compat): diff --git a/example/slider/main.py b/example/slider/main.py new file mode 100644 index 0000000..29da8bc --- /dev/null +++ b/example/slider/main.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +''' + slider + ====== + + Example showing how to add ticks to a QSlider. Note that this does + not work with stylesheets, so it's merely an example of how to + get customized styling behavior with a QSlider. +''' + +import sys + +import slider + + +class Ui: + '''Main class for the user interface.''' + + def setup(self, MainWindow): + '''Setup our main window for the UI.''' + + MainWindow.setObjectName('MainWindow') + MainWindow.resize(1068, 824) + self.centralwidget = slider.compat.QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName('centralwidget') + self.layout = slider.compat.QtWidgets.QVBoxLayout(self.centralwidget) + self.layout.setObjectName('layout') + self.layout.setAlignment(slider.compat.AlignHCenter) + MainWindow.setCentralWidget(self.centralwidget) + + self.slider = slider.Slider(self.centralwidget) + self.slider.setOrientation(slider.compat.Horizontal) + self.slider.setTickInterval(5) + self.slider.setTickPosition(slider.compat.TicksAbove) + self.slider.setObjectName('slider') + self.layout.addWidget(self.slider) + + +def main(): + 'Application entry point' + + app, window = slider.shared.setup_app(slider.args, slider.unknown, slider.compat) + + # setup ui + ui = Ui() + ui.setup(window) + window.setWindowTitle('QSlider with Ticks.') + window.resize(400, 150) + + slider.shared.set_stylesheet(slider.args, app, slider.compat) + return slider.shared.exec_app(slider.args, app, window) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/example/slider.py b/example/slider/slider.py similarity index 73% rename from example/slider.py rename to example/slider/slider.py index 33c9aa3..076289a 100644 --- a/example/slider.py +++ b/example/slider/slider.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # The MIT License (MIT) # # Copyright (c) <2022-Present> @@ -31,9 +29,13 @@ get customized styling behavior with a QSlider. ''' +import os import sys -import shared +EXAMPLE = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.dirname(EXAMPLE)) + +import shared # noqa # pylint: disable=wrong-import-position,import-error parser = shared.create_parser() args, unknown = shared.parse_args(parser) @@ -86,44 +88,3 @@ def paintEvent(self, event): # pylint: disable=unused-argument,(too-many-locals options.subControls = compat.SC_SliderHandle painter.drawComplexControl(compat.CC_Slider, options) - - -class Ui: - '''Main class for the user interface.''' - - def setup(self, MainWindow): - '''Setup our main window for the UI.''' - - MainWindow.setObjectName('MainWindow') - MainWindow.resize(1068, 824) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName('centralwidget') - self.layout = QtWidgets.QVBoxLayout(self.centralwidget) - self.layout.setObjectName('layout') - self.layout.setAlignment(compat.AlignHCenter) - MainWindow.setCentralWidget(self.centralwidget) - - self.slider = Slider(self.centralwidget) - self.slider.setOrientation(compat.Horizontal) - self.slider.setTickInterval(5) - self.slider.setTickPosition(compat.TicksAbove) - self.slider.setObjectName('slider') - self.layout.addWidget(self.slider) - - -def main(): - 'Application entry point' - - app, window = shared.setup_app(args, unknown, compat) - - # setup ui - ui = Ui() - ui.setup(window) - window.setWindowTitle('QSlider with Ticks.') - - shared.set_stylesheet(args, app, compat) - return shared.exec_app(args, app, window) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/example/titlebar/main.py b/example/titlebar/main.py new file mode 100644 index 0000000..ddf1cfe --- /dev/null +++ b/example/titlebar/main.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +''' + titlebar + ======== + + A full-featured, custom titlebar for a subwindow in an MDI area. This + uses a frameless window hint with a custom titlebar, and event filter + to capture titlebar and frame events. This example can also be easily + applied to a top-level window. + + The custom titlebar supports the following: + - Title text + - Title bar with menu, help, min, max, restore, close, shade, and unshade. + - Help, shade, and unshade are optional. + - Menu contains restore, min, max, move, resize, stay on top, and close. + - Custom window minimization. + - Minimized windows can be placed in any corner. + - Windows reposition on resize events to avoid truncating windows. + - Dynamically toggle window state to keep windows above others. + - Drag titlebar to move window + - Double click titlebar to change window state. + - Restores if maximized or minimized. + - Shades or unshades if in normal state and applicable. + - Otherwise, maximizes window. + - Context menu move and resize events. + - Click "Size" to resize from the bottom right based on cursor. + - Click "Move" to move bottom-center of titlebar to cursor. + - Drag to resize on window border with or without size grips. + - If the window contains size grips, use the default behavior. + - Otherwise, monitor mouse and hover events on window border. + - If hovering over window border, draw appropriate resize cursor. + - If clicked on window border, enter resize mode. + - Click again to exit resize mode. + - Custom border width for a window outline. + + The following Qt properties ensure proper styling of the UI: + - `isTitlebar`: should be set on the title bar. ensures all widgets + in the title bar have the correct background. + - `isWindow`: set on the window to ensure there is no default border. + - `hasWindowFrame`: set on a window with a border to draw the frame. + + The widget choice is very deliberate: any modifications can cause + unexpected changes. `TitleBar` must be a `QFrame` so the background + is filled, but must have a `NoFrame` shape. The window frame should + have `NoFrame` without a border, but should be a `Box` with a border. + Any other more elaborate style, like a `Panel`, won't be rendered + correctly. + + NOTE: you cannot correctly emulate a title bar if the desktop environment + is Wayland, even if the app is running in X11 mode. This mostly affects + just the top-level title bar (and subwindows almost entirely work), + but there are a few small issues for subwindows. + + The top-level title bar can have a few issues on Wayland. + - Cannot move the window position. This cannot be done even if you know + the compositor (such as kwin). + - Cannot use the menu resize due to `QWidget::mouseGrab()`. + - This plugin supports grabbing the mouse only for popup windows + - The window stops tracking mouse movements past a certain distance. + - Attempting to move the window position causes global position to be wrong. + - Wayland does not support `Stay on Top` directive. + - qt.qpa.wayland: Wayland does not support QWindow::requestActivate() + + A few other issues exist on Wayland. + - The menu resize has to guess the mouse position outside of the window bounds. + - This cannot be fixed since we cannot use mouse events if the user + is outside the main window, nor do hover events trigger. + We cannot guess where the user left the main window, since + `QCursor::pos` will not be updated until the user moves the + mouse within the application, so merely resizing until the + actual cursor is within the window won't work. + - We cannot intercept mouse events for the menu resize outside the window. + - This even occurs when forcing X11 on Wayland. + + On Windows, only the menu resize event fails. For the subwindow, it stops + tracking outside of the window boundaries, and for the main window, it does + the same, making it practically useless. + + # Testing + + The current platforms/desktop environments have been tested: + - Gnome (X11, Wayland) + - KDE Plasma (X11, Wayland) + - Windows 10 +''' + +import sys + +import titlebar + +# Add a warning if we're using Wayland with a custom titlebar. +if not titlebar.args.default_window_frame and titlebar.USE_WAYLAND_FRAME: + print('WARNING: Wayland does not support custom title bars.', file=sys.stderr) + print('Applications in Wayland cannot set their own position.', file=sys.stderr) + print('Defaulting to the system title bar instead.', file=sys.stderr) + + +class DefaultWindow(titlebar.Window): + '''Default main window with a window frame.''' + + def __init__(self, parent=None, flags=titlebar.QtCore.Qt.WindowType(0)): + if titlebar.args.window_help: + flags |= titlebar.compat.WindowContextHelpButtonHint + if titlebar.args.window_shade: + flags |= titlebar.compat.WindowShadeButtonHint + super().__init__(parent, flags) + + self._central = titlebar.QtWidgets.QFrame(self) + self._layout = titlebar.QtWidgets.QVBoxLayout(self._central) + self.setCentralWidget(self._central) + self._widget = titlebar.QtWidgets.QWidget(self._central) + self._widget.setLayout(titlebar.QtWidgets.QVBoxLayout()) + self._central.layout().addWidget(self._widget, 10) + + if titlebar.args.status_bar: + self._statusbar = titlebar.QtWidgets.QStatusBar(self._central) + self.setStatusBar(self._statusbar) + + self.setup() + + +def main(): + 'Application entry point' + + window_class = titlebar.FramelessWindow + # Wayland does not allow windows to reposition themselves: therefore, + # we cannot use the custom titlebar at the application level. + if titlebar.args.default_window_frame or titlebar.USE_WAYLAND_FRAME: + window_class = DefaultWindow + app, window = titlebar.shared.setup_app( + titlebar.args, titlebar.unknown, titlebar.compat, window_class=window_class + ) + app.installEventFilter(window) + + titlebar.shared.set_stylesheet(titlebar.args, app, titlebar.compat) + return titlebar.shared.exec_app(titlebar.args, app, window) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/example/titlebar.py b/example/titlebar/titlebar.py similarity index 97% rename from example/titlebar.py rename to example/titlebar/titlebar.py index bbfbd76..f23b172 100644 --- a/example/titlebar.py +++ b/example/titlebar/titlebar.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # The MIT License (MIT) # # Copyright (c) <2022-Present> @@ -114,7 +112,10 @@ import sys from pathlib import Path -import shared +HOME = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.dirname(HOME)) + +import shared # noqa # pylint: disable=wrong-import-position,import-error parser = shared.create_parser() parser.add_argument( @@ -180,12 +181,6 @@ IS_TRUE_WAYLAND = 'WAYLAND_DISPLAY' in os.environ USE_WAYLAND_FRAME = IS_WAYLAND and not args.wayland_testing -# Add a warning if we're using Wayland with a custom titlebar. -if not args.default_window_frame and USE_WAYLAND_FRAME: - print('WARNING: Wayland does not support custom title bars.', file=sys.stderr) - print('Applications in Wayland cannot set their own position.', file=sys.stderr) - print('Defaulting to the system title bar instead.', file=sys.stderr) - class MinimizeLocation(enum.IntEnum): '''Location where to place minimized widgets.''' @@ -2087,30 +2082,6 @@ def eventFilter(self, obj, event): return super().eventFilter(obj, event) -class DefaultWindow(Window): - '''Default main window with a window frame.''' - - def __init__(self, parent=None, flags=QtCore.Qt.WindowType(0)): - if args.window_help: - flags |= compat.WindowContextHelpButtonHint - if args.window_shade: - flags |= compat.WindowShadeButtonHint - super().__init__(parent, flags) - - self._central = QtWidgets.QFrame(self) - self._layout = QtWidgets.QVBoxLayout(self._central) - self.setCentralWidget(self._central) - self._widget = QtWidgets.QWidget(self._central) - self._widget.setLayout(QtWidgets.QVBoxLayout()) - self._central.layout().addWidget(self._widget, 10) - - if args.status_bar: - self._statusbar = QtWidgets.QStatusBar(self._central) - self.setStatusBar(self._statusbar) - - self.setup() - - class FramelessWindow(Window): '''Main window with a custom event filter for all events.''' @@ -2319,22 +2290,3 @@ def changeEvent(self, event): self._titlebar.maximize() else: self._titlebar.restore() - - -def main(): - 'Application entry point' - - window_class = FramelessWindow - # Wayland does not allow windows to reposition themselves: therefore, - # we cannot use the custom titlebar at the application level. - if args.default_window_frame or USE_WAYLAND_FRAME: - window_class = DefaultWindow - app, window = shared.setup_app(args, unknown, compat, window_class=window_class) - app.installEventFilter(window) - - shared.set_stylesheet(args, app, compat) - return shared.exec_app(args, app, window) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/example/url.py b/example/url.py index 4256744..5025d44 100644 --- a/example/url.py +++ b/example/url.py @@ -123,6 +123,7 @@ def main(): ui = Ui() ui.setup(window) window.setWindowTitle('Stylized URL colors.') + window.resize(200, 100) shared.set_stylesheet(args, app, compat) return shared.exec_app(args, app, window) diff --git a/example/whatsthis.py b/example/whatsthis.py index 1d7c78e..312f580 100644 --- a/example/whatsthis.py +++ b/example/whatsthis.py @@ -79,6 +79,7 @@ def main(): ui = Ui() ui.setup(window) window.setWindowTitle('Stylized QWhatsThis.') + window.resize(200, 100) shared.set_stylesheet(args, app, compat) return shared.exec_app(args, app, window) diff --git a/extension/README.md b/extension/README.md index 0816213..4bd5cdc 100644 --- a/extension/README.md +++ b/extension/README.md @@ -1,5 +1,4 @@ -Extensions -========== +# Extensions Extensions enable the creation of stylesheets using the same, customizable themes of the original stylesheet. This both allows refining the generated stylesheet and supporting third-party Qt extensions/widgets. @@ -7,7 +6,7 @@ Extensions are optionally added to the generated stylesheets, allowing you to ex Furthermore, this simplifies making local, application-specific changes, without having to deal with merge conflicts when fetching updates. -# Pre-Packaged Extensions +## Pre-Packaged Extensions ### Advanced Docking System @@ -22,34 +21,34 @@ python configure.py --extensions=advanced-docking-system And make sure to [disable](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/blob/master/doc/user-guide.md#disabling-the-internal-style-sheet) the internal stylesheet in the dock manager.
- Advanced Docking System View 1
- Advanced Docking System View 2
- Advanced Docking System View 3
- Advanced Docking System View 4
@@ -64,10 +63,10 @@ python configure.py --extensions=dock-tooltips ```
- Dock Tooltips
@@ -168,7 +167,7 @@ The following is a 1:1 mapping of the standard icon enumerated name and the icon } ``` -# Creating Extensions +## Creating Extensions Creating extensions extends the existing stylesheet configurations, and adds custom rules to the stylesheet, which can then be configured for all themes. This supports custom icons, rules, and more. @@ -211,7 +210,7 @@ Next, let's create an SVG template for the icon, at `icon.svg.in`: ``` -Here, `^0^` signifies index-based replacement, so we must define an entry in +Here, `^0^` signifies index-based replacement, so we must define an entry in `icons.json` to specify how we should do the replacement. ```json diff --git a/scripts/cmake.sh b/scripts/cmake.sh index 79b3933..6eac38c 100755 --- a/scripts/cmake.sh +++ b/scripts/cmake.sh @@ -21,7 +21,6 @@ mkdir -p "${build_dir}/"{qt5,qt6} # we xcb installed for our headless running, so exit if we don't have it if ! hash xvfb-run &>/dev/null; then >&2 echo "Do not have xvfb installed..." - exit 1 fi # first, try Qt5 @@ -31,16 +30,20 @@ export QT_QPA_PLATFORM=offscreen cd "${build_dir}/qt5" cmake "${project_home}/example/cmake" -D QT_VERSION=Qt5 make -j -timeout 1 xvfb-run -a ./testing || error_code=$? -if [[ "${error_code}" != 124 ]]; then - exit "${error_code}" +if hash xvfb-run &>/dev/null; then + timeout 1 xvfb-run -a ./testing || error_code=$? + if [[ "${error_code}" != 124 ]]; then + exit "${error_code}" + fi fi # first, try Qt6 cd "${build_dir}/qt6" cmake "${project_home}/example/cmake" -D QT_VERSION=Qt6 make -j -timeout 1 xvfb-run -a ./testing || error_code=$? -if [[ "${error_code}" != 124 ]]; then - exit "${error_code}" +if hash xvfb-run &>/dev/null; then + timeout 1 xvfb-run -a ./testing || error_code=$? + if [[ "${error_code}" != 124 ]]; then + exit "${error_code}" + fi fi diff --git a/scripts/lint.sh b/scripts/lint.sh index 892d686..430e693 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -16,10 +16,10 @@ cd "${project_home}" # on the path by default. if ! is-set PYTHON; then pylint ./*.py example/*.py example/**/*.py - pyright example/breeze_theme.py + pyright example/detect/system_theme.py flake8 else ${PYTHON} -m pylint ./*.py example/*.py example/**/*.py - ${PYTHON} -m pyright example/breeze_theme.py + ${PYTHON} -m pyright example/detect/system_theme.py ${PYTHON} -m flake8 fi diff --git a/scripts/test_theme.cpp b/scripts/test_theme.cpp index a9f2fae..93acdaf 100644 --- a/scripts/test_theme.cpp +++ b/scripts/test_theme.cpp @@ -3,7 +3,7 @@ */ #include -#include "../example/breeze_theme.hpp" +#include "../example/detect/system_theme.hpp" int main() { diff --git a/scripts/theme.sh b/scripts/theme.sh index a3794da..0c9e480 100755 --- a/scripts/theme.sh +++ b/scripts/theme.sh @@ -6,19 +6,19 @@ set -eux pipefail scripts_home="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" project_home="$(dirname "${scripts_home}")" -cd "${project_home}/example" +cd "${project_home}/example/detect" # shellcheck source=/dev/null -. "${scripts_home}/shared.sh" +. "${scripts_home}/../shared.sh" if ! is-set PYTHON; then PYTHON=python fi # Check the import first, then calling the function for easier debugging. -${PYTHON} -c "import breeze_theme" -theme=$(${PYTHON} -c "import breeze_theme; print(breeze_theme.get_theme())") +${PYTHON} -c "import system_theme" +theme=$(${PYTHON} -c "import system_theme; print(system_theme.get_theme())") if [[ "${theme}" != Theme.* ]]; then >&2 echo "Unable to get the correct theme." exit 1 fi -${PYTHON} -c "import breeze_theme; print(breeze_theme.is_light())" -${PYTHON} -c "import breeze_theme; print(breeze_theme.is_dark())" +${PYTHON} -c "import system_theme; print(system_theme.is_light())" +${PYTHON} -c "import system_theme; print(system_theme.is_dark())" diff --git a/setup.cfg b/setup.cfg index c7c01a4..499c9d4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,6 @@ per-file-ignores = example/widgets.py: F841 test/ui.py: F841 # lambdas are way cleaner here - example/titlebar.py: E731 + example/titlebar/titlebar.py: E731 # these are auto-generated files - resources/*.py: E302 E305 \ No newline at end of file + resources/*.py: E302 E305