From 4922fec63c07dcfa4c75c79c0ec6578cfc62dfa2 Mon Sep 17 00:00:00 2001 From: Jamie Murphy Date: Mon, 8 Aug 2022 02:56:49 -0700 Subject: [PATCH] banner: Add AdwBanner --- demo/adw-demo-window.c | 2 + demo/adw-demo-window.ui | 10 + demo/adwaita-demo.gresources.xml | 2 + .../actions/widget-banner-symbolic.svg | 9 + demo/meson.build | 1 + demo/pages/banners/adw-demo-page-banners.c | 67 ++ demo/pages/banners/adw-demo-page-banners.h | 11 + demo/pages/banners/adw-demo-page-banners.ui | 71 ++ doc/images/banner-dark.png | Bin 0 -> 10681 bytes doc/images/banner.png | Bin 0 -> 10509 bytes doc/tools/data/banner.ui | 25 + doc/widget-gallery.md | 7 + src/adw-banner.c | 652 ++++++++++++++++++ src/adw-banner.h | 52 ++ src/adw-banner.ui | 42 ++ src/adwaita.gresources.xml | 1 + src/adwaita.h | 1 + src/meson.build | 3 + src/stylesheet/widgets/_misc.scss | 12 + tests/meson.build | 1 + tests/test-banner.c | 81 +++ 21 files changed, 1050 insertions(+) create mode 100644 demo/icons/scalable/actions/widget-banner-symbolic.svg create mode 100644 demo/pages/banners/adw-demo-page-banners.c create mode 100644 demo/pages/banners/adw-demo-page-banners.h create mode 100644 demo/pages/banners/adw-demo-page-banners.ui create mode 100644 doc/images/banner-dark.png create mode 100644 doc/images/banner.png create mode 100644 doc/tools/data/banner.ui create mode 100644 src/adw-banner.c create mode 100644 src/adw-banner.h create mode 100644 src/adw-banner.ui create mode 100644 tests/test-banner.c diff --git a/demo/adw-demo-window.c b/demo/adw-demo-window.c index 58fe91ea6..b90a389f9 100644 --- a/demo/adw-demo-window.c +++ b/demo/adw-demo-window.c @@ -5,6 +5,7 @@ #include "pages/about/adw-demo-page-about.h" #include "pages/animations/adw-demo-page-animations.h" #include "pages/avatar/adw-demo-page-avatar.h" +#include "pages/banners/adw-demo-page-banners.h" #include "pages/buttons/adw-demo-page-buttons.h" #include "pages/carousel/adw-demo-page-carousel.h" #include "pages/clamp/adw-demo-page-clamp.h" @@ -119,6 +120,7 @@ adw_demo_window_init (AdwDemoWindow *self) g_type_ensure (ADW_TYPE_DEMO_PAGE_ABOUT); g_type_ensure (ADW_TYPE_DEMO_PAGE_ANIMATIONS); + g_type_ensure (ADW_TYPE_DEMO_PAGE_BANNERS); g_type_ensure (ADW_TYPE_DEMO_PAGE_AVATAR); g_type_ensure (ADW_TYPE_DEMO_PAGE_BUTTONS); g_type_ensure (ADW_TYPE_DEMO_PAGE_CAROUSEL); diff --git a/demo/adw-demo-window.ui b/demo/adw-demo-window.ui index 6cd37fc43..2b991b1c7 100644 --- a/demo/adw-demo-window.ui +++ b/demo/adw-demo-window.ui @@ -236,6 +236,16 @@ + + + Banner + + + + + + + diff --git a/demo/adwaita-demo.gresources.xml b/demo/adwaita-demo.gresources.xml index f470e937e..eb839f9b1 100644 --- a/demo/adwaita-demo.gresources.xml +++ b/demo/adwaita-demo.gresources.xml @@ -26,6 +26,7 @@ icons/scalable/actions/view-sidebar-end-symbolic.svg icons/scalable/actions/view-sidebar-end-symbolic-rtl.svg icons/scalable/actions/widget-about-symbolic.svg + icons/scalable/actions/widget-banner-symbolic.svg icons/scalable/actions/widget-carousel-symbolic.svg icons/scalable/actions/widget-clamp-symbolic.svg icons/scalable/actions/widget-dialog-symbolic.svg @@ -48,6 +49,7 @@ pages/about/adw-demo-page-about.ui pages/animations/adw-demo-page-animations.ui pages/avatar/adw-demo-page-avatar.ui + pages/banners/adw-demo-page-banners.ui pages/buttons/adw-demo-page-buttons.ui pages/carousel/adw-demo-page-carousel.ui pages/clamp/adw-demo-page-clamp.ui diff --git a/demo/icons/scalable/actions/widget-banner-symbolic.svg b/demo/icons/scalable/actions/widget-banner-symbolic.svg new file mode 100644 index 000000000..ccf644625 --- /dev/null +++ b/demo/icons/scalable/actions/widget-banner-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/demo/meson.build b/demo/meson.build index ee805e78e..8127a1791 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -26,6 +26,7 @@ adwaita_demo_sources = [ 'pages/about/adw-demo-page-about.c', 'pages/animations/adw-demo-page-animations.c', 'pages/avatar/adw-demo-page-avatar.c', + 'pages/banners/adw-demo-page-banners.c', 'pages/buttons/adw-demo-page-buttons.c', 'pages/carousel/adw-demo-page-carousel.c', 'pages/clamp/adw-demo-page-clamp.c', diff --git a/demo/pages/banners/adw-demo-page-banners.c b/demo/pages/banners/adw-demo-page-banners.c new file mode 100644 index 000000000..fd1806a0b --- /dev/null +++ b/demo/pages/banners/adw-demo-page-banners.c @@ -0,0 +1,67 @@ +#include "adw-demo-page-banners.h" + +#include + +struct _AdwDemoPageBanners +{ + AdwBin parent_instance; + + AdwBanner *banner; + AdwEntryRow *button_label_row; +}; + +enum { + SIGNAL_ADD_TOAST, + SIGNAL_LAST_SIGNAL, +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +G_DEFINE_TYPE (AdwDemoPageBanners, adw_demo_page_banners, ADW_TYPE_BIN) + +static void +toggle_button_cb (AdwDemoPageBanners *self) +{ + if (g_strcmp0 (adw_banner_get_button_label (self->banner), "") == 0) { + adw_banner_set_button_label (self->banner, gtk_editable_get_text (GTK_EDITABLE (self->button_label_row))); + } else { + adw_banner_set_button_label (self->banner, NULL); + } +} + +static void +banner_activate_cb (AdwDemoPageBanners *self) +{ + AdwToast *toast = adw_toast_new (_("Banner action triggered")); + + g_signal_emit (self, signals[SIGNAL_ADD_TOAST], 0, toast); +} + +static void +adw_demo_page_banners_class_init (AdwDemoPageBannersClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + signals[SIGNAL_ADD_TOAST] = + g_signal_new ("add-toast", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + ADW_TYPE_TOAST); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Adwaita1/Demo/ui/pages/banners/adw-demo-page-banners.ui"); + gtk_widget_class_bind_template_child (widget_class, AdwDemoPageBanners, banner); + gtk_widget_class_bind_template_child (widget_class, AdwDemoPageBanners, button_label_row); + + gtk_widget_class_install_action (widget_class, "demo.toggle-button", NULL, (GtkWidgetActionActivateFunc) toggle_button_cb); + gtk_widget_class_install_action (widget_class, "demo.activate", NULL, (GtkWidgetActionActivateFunc) banner_activate_cb); +} + +static void +adw_demo_page_banners_init (AdwDemoPageBanners *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/demo/pages/banners/adw-demo-page-banners.h b/demo/pages/banners/adw-demo-page-banners.h new file mode 100644 index 000000000..20787d663 --- /dev/null +++ b/demo/pages/banners/adw-demo-page-banners.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define ADW_TYPE_DEMO_PAGE_BANNERS (adw_demo_page_banners_get_type()) + +G_DECLARE_FINAL_TYPE (AdwDemoPageBanners, adw_demo_page_banners, ADW, DEMO_PAGE_BANNERS, AdwBin) + +G_END_DECLS diff --git a/demo/pages/banners/adw-demo-page-banners.ui b/demo/pages/banners/adw-demo-page-banners.ui new file mode 100644 index 000000000..e66a7ef74 --- /dev/null +++ b/demo/pages/banners/adw-demo-page-banners.ui @@ -0,0 +1,71 @@ + + + + + + diff --git a/doc/images/banner-dark.png b/doc/images/banner-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..795f4d63bdc8f5ac9037e24f1c58ded6717a70ca GIT binary patch literal 10681 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 S>nZdz5Wv**9+ay-4*ws9u1yU9 literal 0 HcmV?d00001 diff --git a/doc/images/banner.png b/doc/images/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..75d4770325734e59ca9bbcd69570bca00abed7f1 GIT binary patch literal 10509 zcmdUVc{CJY*zc6Ikfl<}u2dqlkzJI1-zK}V#0*2ljD0Dg$P(GJWX&*y8M0SG*$rcu zF;p1qkQh62U)}HC^Zjwpcka38e1F_I#~(A^<$0g&_j{iAePv{z$;QIN0)ar-w6*S; zKp+S45Xb?}qldvSnYoECArK*m_B~ZI-*+^M-#adPCS%#hr+4+*k>|ez&OZ2ahyB$T zCPSx{==8IyK7_M*U)=IEWG?Z>ot=30lDeVoeS7m_e~z1oXupu!(XnXJr{9H-{??A~ zJete*^|+V!`chn)06H2yH#b*Ow@nXJ-0G(w1GlZizXm9;6vK}J8U8yyq2kX14Rek@ zF*pDTwQv2a^vpqR#w&8=5Co#b_Fm!GL5K4_M<9^Un+H+#CLFN^!8<^RS+S7^ws6(T z!B}pf5~9u}ixjnwSHm5_q(6DN?1Wan8_}D?U?7XO@!>fK){It^BKX!?=&FXSIAo|I z5Ggplzd@p4ONrq0+XRQB0kw(|4zl)OkipY)k6GpQxSw9@qK-0I3J5|VIq~1#Qu?gp zpVe`;ILJ`>y1u6FuXZ&({5q?c5w}%*&tPD&EE5FcWN=A*i1}0@>lM911!Cx|TuL?@ z1Y+dcaEO$;|0UBaoRf1PEsywT0ky3r8?wLMXvC0N3*wR*uAB)s9+pGgEa1sWL}+qI zLm;BkKm2n{*?!vHt?+EYW`}vp3(Jo*8O$@ulpg@n8+TE011Qvdw3yzuv}oZ#`+|%} zMoPd42awp}B5OBlp7Mg`y;UC65_h2A?WKYAQ5Si|?PZ&T5QxPxY015&2i`~&T0{!S zP1pRHmTr(YPsb#WNEoik{ip_q5p|lyKFR0#&$8zTvO~BNjLS9mbI4jy?TsV{jomOX&04Dpt#V$k0mD7%&sfjPGnicJj%dt zA|)pk!-;4@WZH(V;m`m6CN+H;m{8JGg6oGEfx1p5&z#mzUAz0UQ{$zK=kzK`zdn+j z%n6oKn=15wS30)79!9bJ5(gb(zx0wgM z1|Mcprsc293T1Y4X2L^J*8L!mM43IrJBkm_737+jVaoQ$zEg;@th#SG} zpZOdaXtRPs1r4f3`hhjMNQtB^_o-l9AG8LoQNS~t5@7x=0 zLuI5`=YC$>cP#ktbD{!oX(XB7mK|q5#5hFIZ#1wEm#&|u>?#F4C@V0HF+i=y;O}EJV z1~uhgbBU-^xpyOiIz*Ccq*yxw;%;$Y1U}dN=cHs_gyuby=zJ({>b#dPyOm-)(Gur5 ztfVW)-ZJ~gFi!HUS^PbIakKtWU&%cBVp9>`&L>V19SZ$k{%)S_%%+B705+0QnW`&S_3Ie$^ zcIY7Dg!tLx9(JD||5BQ0iwQ6{31i2G-1E+PiXfbHAb3jD)+4g`l1F}M$FEq&SEI$+ z$E}%RUiR49A~{#9GaP+w*KBcqE7TuJNlDY%hg^Z76_)Q%hKD!wY;7qBIO{+gCw~ay z_&{40yd;{XTiCji17B(3QQ#DYC37N>yb4z;7;<}KGWR^m)ijRxMl)sy!SO>Cp53|g zM$748xm{yfl2@usr0WVr>2>){%cLIdkDcZWPZ_5A+RLOs^UbYj9n>DZfn$?RIf=i;OEF3ofCb#looU&MU; z!gdZ#Qu6Id@!mFQYz}F0$ar_eCA&Mi)nWH2KsE@_?E&V4&m<^yNCj)WGLF*N;@khQ zfzUEK^l)9`6?AvtQ4?(GySlo3QuOieg(ga0F%M$rM>}8B-Gq)u-RVd%VIGh@lqxLr zDlAIJOD(Gm%&jZ%8x%hL7axMSBN(G`I!1$l$LB^w95y#I`%qbVow>HOpSmF5BMJ*I(J1w|r-IVc)6Gmx^P-}nyp_i!z0z)` zk^*tb;{H=EBM!NahO=WV>BfCij zkdz@pY;UwgFgn)rUqh zZ;4ythqnb`TTkSYBIGC@XhxU`OxGn!~CDr?s$0~2R>Ze|Nf1$6veTGnyLha5d zQV!ahd3HqL`>%5cd>FR1M$7PCIL
    }~^v`XnqY@seI@-?DiBW7$&m=rgdNpP%vEaC!;F zSQQjy`Rf25zi;zIz5T3I!=LB=>x-BJOov_RbA)smM0%|JoM;S>bO&9=XFRATS;D&T zx?`It7Yl4}XR~g1jZFU;#vyjry}oIuAjK|0guXOxPp8vs<<%jTLL=(WYQ}@>7aLX# z5Tla(q zVzFkNz`kPr*A~)-hObn1x?PW4ZwdQ-YbDUD@Bie-(Q~C7M{s!K`?QQ6KOj@8wo* zx1M)$R`n>12&^+%qHeY@W3b3jkm5TL!*lIHXwypOl5Fe*RmX2dW|{SsMhs7ZTY00* zLw;_hgRNK29+!v4f`}}5b?P#Fh1$)p)NftmLHPFSlr#)4f~1iMPuyiMXffT1Xchmq z9L+|=SQmka$$b3vm^?Mrkt@2DE84j!=f;E25auvdjCGB{KGq){f<)aH<$8J?pLp4D z@o0bICH({u%9D0}QXR77l3`XUh~D1+SopABwz4fC4lQ3we?vB284-Too*jYw*|ft(oLPZ?wY3r4OhW|1EXl2$kb2!w zM7t%DgLpB;Cb!mWdTw(%oA|*v&%5JFk$2z)hf5#^RrkF!TB_;8Cp98izHl$<3*BeHOB_}GU;kYNXaE$^4uctfq}Z5*6mQ(cFrtv z=L9@N(k+BPWU$F5O^{&SeX4g* z4gd}efKA!aHh^YA{+y>kLk9CT@EzU_tRnf=WmibDHP z0ukT@bQS{P_{Tv)|1Apn%(OoR^7DWyU=)x)p+W%hA-;Fw|MRrBllNaNjZ|p@BpM$8 z{}yEd#~wHR9t}zaYwG~C^vTZk{Y9eGEU(5dU72LY>>uX8+`uTohX%&DlEyzIQ!jbE znim4NuW0i=${=YoD@y9bKJV6krEwlUyv}@p6b_hzmSdPzdWM-f#q!I^Mtdy3l>#`a zw(z|oN&H^Rk(2*s`~d4|y<7uy;sY$iv8m{n_`c$dZh(n@mBOF5ZR+0JKiI`ExVOJ- z$gBT5J#2LJ@RM;~?Q4OCOdoO0PqIBeJuO|V-&J9^y1h~{J{uw4J{N{8v5oVYUzZyE z#^B4Y*mU++90sASrR&(DZ0q&KRex*Q`3NRp=lzvB>hpeopKcc7gl)*IW!>g@Z|Cri z$+_}cSThgr+LbfWj}}%2(hMs;DwIPz*Kfx%af&wm+PK|$b8<+0N6Rl8!_yEGljJ4Pzu@FPewaRBulRNe z207=oFXhV#17r0fokrXaQa#T&b4O~t&vM$_O1R{3L>G66TeVNKuw@_z&>-Q(q zN>pjVOCdHPm$K&7t1*hp-bi#QzT+Y*SB!@DR@yXdLH#~y@l=ckeY8|%K89#FIIU1! zV%U6uXB#6f9XR>w7+I0gH(pd&SzE8JSeFeY#|_P= z^In2YYCFmg={GVYB?5MxweghOcEtb$#&?~Y=H*hS7j9)I51moEPA6Jb%$x?PG!sRY zjoF2g>3PetyGrztQpGoFakU7^qZ;?Ux5y zMS*np#+~)%Ik#z>4^PJTe6&qW2ZEJu?y6$!_Ud@nX@p;QHD+?fLq04jZS#5Sm5ll7 zuk3B5Dv#6|ClLs96rY^3y=7bUV&KaOS=2OPHqajUfEY)l^wgVX=T+a#*aP1Soy(g` zrI1SncDP;KUZb@C!=B@G!CSJzXL-BR_j1m3XBUxznWTL;v@k(l#f8d`Q!b7EWo46p zYVlP@$)5lpzJ7ToQG%CHY&xat*qyPNsyx=9TC~$S@?z#{4vy)Lfkg0>9LBQ3B4~?k zut{pobP44yGIM5yJfL42dgH3^Y2r>5OS+Lhq_twwMnv>Oj)MXX1lQ05s z9O%HF1~8_)YpIJsh?cdoRrz&$6)E9+y7=#jZ67=t{@VCim#h*`J6}H?uWP*{FlDxY z<~~xAF2U!s5N)K<)GIsC%nf=qStPvWv=zNg4Y_1qQvS}j4$wTicPrdV3M&{ z#P~3vDKn=ua*{md`jl}}Qi^r(qJpOcQ8#2edW%g_kJ|?ICu!}1p`xugcXKW#L&kMAZ5loP zxy80u_+gJE)zo&t@)W}@y`!^bi<}tex|kR@w~H+CUah!XoNHPTyxBLvPxfFql`L+2 zX|<|<3e$9!9`PGpTYl7t3w+Zw*q9Q$)fvgfk~Et%oi%jIRB&cEq*GH6-raFgerq_< zn-I77Rz&*tZCLB?``F!V>%n?Y#tXT7=T8uZ2=l8|J-Qz(F7vi8HjpH05*L@{`I&y2 z5B~Z(c}J*kS^n(tD`rDyY7g^`ok6V!SCF4Jhj?sifxP*B3{eYl%bp45O=O20l7!Aa}x^?p$GL4g@pAcfC3_Y2?!TAoqQAH2BEY z-`%b6zgn{{dL~h_ky;pg=Gg7gF5xHJlPg!m#g|fBaxO&b!b?orF(IB9YHvm{n_%ex zcL3_jnZ&h4{T)r>tYUB_GEW5d{iUM$a*U zyD2a_#=6l)rGD}Jzl562u+4l-I(H=|G1fbdID0nK%-kDoB0H)bfC?_OxSGtFKEa>} zk{XMqn!Y^v{$yc;0?+N&YOhQ#yu{39^eHznzZ!Z;JF0boF(x6-QL+@;f(;-}`|nOQ zmoRd^NX8y7KEDr2MIB=1X5RR+UKOPo!MDDx1p&Qd$=eYLJ7M_2!L^c2t$duLrPn|( z^FhE{tfhYLMO23vb*WTX@Thq2<$KB4u3f&@qh0NyTsn$J$jW1- z+n4vWC;{U04SJe39jeTq-MBa{=& z#Bm=$xrFbpwAiWapsiO!82`Jrb-~&clO1XQ9b!Sy7JK|4l;#eDC5!AQW{2xY?7?r0 ze#`o&+U+t&x{VJ6P0#U~Sy~ocWX&SZIhR{|a5D%c6$x{X*fl(;6O)2V&6womM{I&} zI{ThKEopl(5}lZ27eu0dd^B|uSB(wt@?{n;Go;)B5Vkwfh(phgnm59?eH6QrRzZix zLT~Hua?xDnv6`6-ZY~C$UR#9IB_+y&~+<}aX*qn&Wa%YiaVXo`#4o(Rw{0% zMuX2MYTr=^Yl3gRfl;O%i!_(4wDDh4s5%KUm=Hq@24hgqmk;tPa>H12zQu<)GnTzm zzr`-QkRvEi7hD{vku7IXuDJd>c(ZD0{#(Dx98e5l_>P6|~j6<-2m2HoRv@ z=k@*6bJnM#gh7mH3!c|eQrhLP+Uf9;pPO-%-rH8`A1&I`Tx!7CEH&Y5m^f5)rUU1r zVs)@0wtIp#@Wn(%-uh6}2x>icEQo*){KK(yId-!SDruV&(!i*Gq#t4gu*{Lv9?JxB zC?54K{zTno);MhKlqt@6B64dr5ySI%_(9Oh*G}YQpQ8!S{LV5F);73JpNsaowIH77 zO=t6gQL*E0fK6DfP*1Y)DC$jztk=bMl#AE~3^;WMF05PCY_UoB?wzBT(u$I)g<);V zwAWAk9e?R=J)pq50Rge4wd?je<2e_|OZPL} z0dIiUj7B@PXDpnBNFC-wy)A2mRZhsP@9h=8Sf^_qpZ$b$90km12>cmXpuTK;gI=kc zOTX*gpQNIf*2@7Ja)*NDs0 zlI4xfKE`e$IQyi7cZ37z;UnpLeO$IryV-bx+G;nm42F_O%U>HQ3M!rJTr74eckB7F zrB}K7V46ekuEJ{aU*1{6~Fvj zOWO+P{$8ZmlhN|`dSHRlDIc6mVR14s8PXm-CXtY4MnlOOAuJv1i;s1Wt zF-N+5^^Q_|ACE>WOb75Zwa}LK>%U_WYG}FqlJvFP=z?19{Q}kWF9wkRzmJ)dTzUUh ze+gpp<|jGdoY^lJ|Lfv6M`Vd~9sWRe+!PGF^DOWD|DhKCe=MQ@Kb$tgg0Hw5!wOW9 zFD5>M?GwizLIEp&HSe>Ov{$XmG2Cw$1YVRHsq7QOdl;M)21WnpkSNLaO(iYv!5`qB z`Hse>JnNRyzurUBzsmc6cOo_%4u=_ek8HuD30rRsT@=+4X2* zo6E|)macARz(|GwSPBN~g`PMBcqIgK^!xr>081AsOT`R(>&A7Z=EJc1L=SqTul?kMfgh5~Bv+H7x5~G48_10eOt!TFHTxJXpuuD+3LnR);_m2D6IOC!3rvScc1I>ZO{Flxo$V8*dmZ z6XVDF3SL8SV|`I*o%>}L6~EtsX6#lES1h=UhR}veExm{p?E(t-P4Y&~^MI%h;Z_k- z)OFM@-j8)GoF|{D(D?^R;W?bOcUg!(#LRQ}<1cd1_S){7?YrQD^79bJTJS@8dA2Yt z`(&6`3Hz#UJ#0RnoqcH8xzb`WLnw1;DAqguJH{Y)pq$-7XQ$=04!pFyoY9#s?Y5}n zydT^J?%nCqJ_I6>p^iv$b*zpWKWM0v)$L^ALEUC$E2u`h%Q*jiezz!^&ACca@`2LGFHj zn-8>=Z1_O`>(2EpY(c?PBWQO!wYG4mG|Y?zEKy3USy`>Ug%sOECOkc+U#esT^kk-(An-DWBi1f2KtWAs(6g#U8P6Od8>XcR!PtRE}8Q zd!af|^`vnV>VKl21l(08(mcCT&Mcz@Pu0N=WXiu=#Q$0&9}oHxrz(G%`q|wET;MMM zL3q$#dh*^xxRhySkhOrJqtXcK&kQ64pu*%&%})GiY9B=Cd1Xs{Eg-A}O+My+AFZW+=JKbAOaa)WcES=d8kK+`nrxlglyu z_%9MXD4#zcN$r7O-d`QdXlYzt^EDn&rZVE+ahB~r;wd>3TrAfk)7;FFs8f~2X5g8K+H^td zSACp(CMRthj-6Z*Lf{bSha{M7WiY(RFF)!t@XV(Y6eAe-qsGIoMaZ+-mp z+Yw=K#k`S8guXHhNKa&*c+TE$vKMAAuS#mTnnX7sn}mY}9+w$hF0r{>UtlXXpLk_N zNTNWnu)A}BZLkd)EUN(<&odXn^i5*TZ)66j9wM_R6r}w080Em$A zE?P=nKECi5lJ~ZbvX*Wdc-H_9-In$aB#2N!ryHe(JPoGDrhiQCT66Al?Npv9q{L=i z4cwkp1YR(yR*A025Sqt*&FuK(H&GKhhyVP1Ssy zK`N(xkauyj2P>;3=(NgZ1~ndbc#aKokAO{7rzxUTvNVB6m(z~t&zhB*?(&WQaf9f& z*x9YI6)x|;v?&msdzkJ0{A0Nqc5+-uVoF&rW1YTI3k3OI;7wBMa-8%ZFI^6>(DMmS zlT~icz5q6I|8Xx=-{-h}glq9NwOsp_$HMRSsnqSt6VP4GuT|*yuMY~TR$A@Hfl0M) zoDK})&zWG6Fne0GPoEe+R{8I+s0l}CllHF&{`QvBgyc-WAGnd>B{jJM&?la470ou;cL{tXQO@v0%2r-IDe4%4&lFl^-Et|`>%F65MTJo_Rn+eas~OrU+pUR!DFvG z6<0REkghLPsc~SdoOmvMUm3CRkCrU$M}hJ*)z9Tj=twvvla|3YlUBx&E_o&#$^Yvm zUD{7~jd;Cpn?R_?(8@0(uYb<4PlIOpTb_enCl7t=ns~SWcIW&)aQwG?zBe*B8m%Xm U#0R-s_czd1H@H`(_Tc&d0O<+-fB*mh literal 0 HcmV?d00001 diff --git a/doc/tools/data/banner.ui b/doc/tools/data/banner.ui new file mode 100644 index 000000000..bb2f3900b --- /dev/null +++ b/doc/tools/data/banner.ui @@ -0,0 +1,25 @@ + + + + + + Banner + 600 + 150 + + + vertical + + + + + + True + Unlock to change settings + Unlock + + + + + + diff --git a/doc/widget-gallery.md b/doc/widget-gallery.md index 88767e7a7..4145cf7f7 100644 --- a/doc/widget-gallery.md +++ b/doc/widget-gallery.md @@ -19,6 +19,13 @@ Slug: widget-gallery toast-overlay ](class.ToastOverlay.html) +### Banner + +[ + + banner +](class.Banner.html) + ### Avatar [ diff --git a/src/adw-banner.c b/src/adw-banner.c new file mode 100644 index 000000000..daeb71f3c --- /dev/null +++ b/src/adw-banner.c @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2022 Jamie Murphy + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include "adw-banner.h" +#include "adw-gizmo-private.h" + +#include "adw-macros-private.h" + +#define SPACING 6 +#define LABEL_MAX_WIDTH 500 +#define BUTTON_MAX_WIDTH 160 + +/** + * AdwBanner: + * + * A bar with contextual information. + * + * + * + * banner + * + * + * Banners are hidden by default, use [property@Banner:revealed] to show them. + * + * Banners have a title, set with [property@Banner:title]. Titles can be marked + * up with Pango markup, use [property@Banner:use-markup] to enable it. + * + * Title can be shown centered or left-aligned depending on available space. + * + * Banners can optionally have a button with text on it, set through + * [property@Banner:button-label]. The button can be used with a `GAction`, + * or with the [signal@Banner::button-clicked] signal. + * + * ## CSS nodes + * + * `AdwBanner` has a main CSS node with the name `banner`. + * + * Since: 1.3 + */ + +struct _AdwBanner +{ + GtkWidget parent_instance; + + AdwGizmo *gizmo; + GtkLabel *title; + GtkRevealer *revealer; + GtkButton *button; +}; + +static void adw_banner_actionable_init (GtkActionableInterface *iface); + +G_DEFINE_FINAL_TYPE_WITH_CODE (AdwBanner, adw_banner, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, adw_banner_actionable_init)) + +enum { + PROP_0, + PROP_TITLE, + PROP_BUTTON_LABEL, + PROP_REVEALED, + PROP_USE_MARKUP, + + /* Actionable properties */ + PROP_ACTION_NAME, + PROP_ACTION_TARGET, + LAST_PROP = PROP_ACTION_NAME, +}; + +static GParamSpec *props[LAST_PROP]; + +enum { + SIGNAL_BUTTON_CLICKED, + SIGNAL_LAST_SIGNAL +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +static void +button_clicked (AdwBanner *self) +{ + g_assert (ADW_IS_BANNER (self)); + + g_signal_emit (self, signals[SIGNAL_BUTTON_CLICKED], 0); +} + +static void +adw_banner_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AdwBanner *self = ADW_BANNER (object); + + switch (prop_id) { + case PROP_TITLE: + g_value_set_string (value, adw_banner_get_title (self)); + break; + case PROP_BUTTON_LABEL: + g_value_set_string (value, adw_banner_get_button_label (self)); + break; + case PROP_REVEALED: + g_value_set_boolean (value, adw_banner_get_revealed (self)); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, adw_banner_get_use_markup (self)); + break; + case PROP_ACTION_NAME: + g_value_set_string (value, gtk_actionable_get_action_name (GTK_ACTIONABLE (self))); + break; + case PROP_ACTION_TARGET: + g_value_set_variant (value, gtk_actionable_get_action_target_value (GTK_ACTIONABLE (self))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +adw_banner_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AdwBanner *self = ADW_BANNER (object); + + switch (prop_id) { + case PROP_TITLE: + adw_banner_set_title (self, g_value_get_string (value)); + break; + case PROP_BUTTON_LABEL: + adw_banner_set_button_label (self, g_value_get_string (value)); + break; + case PROP_REVEALED: + adw_banner_set_revealed (self, g_value_get_boolean (value)); + break; + case PROP_USE_MARKUP: + adw_banner_set_use_markup (self, g_value_get_boolean (value)); + break; + case PROP_ACTION_NAME: + gtk_actionable_set_action_name (GTK_ACTIONABLE (self), g_value_get_string (value)); + break; + case PROP_ACTION_TARGET: + gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self), g_value_get_variant (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +adw_banner_dispose (GObject *object) +{ + AdwBanner *self = ADW_BANNER (object); + + gtk_widget_dispose_template (GTK_WIDGET (self), ADW_TYPE_BANNER); + + G_OBJECT_CLASS (adw_banner_parent_class)->dispose (object); +} + +static void +measure_content (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + AdwBanner *self = ADW_BANNER (gtk_widget_get_ancestor (widget, ADW_TYPE_BANNER)); + gboolean button_shown = gtk_widget_is_visible (GTK_WIDGET (self->button)); + int label_min, label_nat; + int button_min, button_nat; + int min = 0, nat = 0; + + gtk_widget_measure (GTK_WIDGET (self->title), orientation, for_size, + &label_min, &label_nat, NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), orientation, for_size, + &button_min, &button_nat, NULL, NULL); + + if (orientation == GTK_ORIENTATION_VERTICAL) { + if (button_shown) { + if (for_size > 0) { + int label_width_nat, button_width_min; + int avail; + + gtk_widget_measure (GTK_WIDGET (self->title), GTK_ORIENTATION_HORIZONTAL, -1, + NULL, &label_width_nat, NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), GTK_ORIENTATION_HORIZONTAL, -1, + &button_width_min, NULL, NULL, NULL); + + avail = (for_size - MIN (label_width_nat, for_size)); + + if (avail <= button_width_min + SPACING) { + min = label_min + SPACING + button_min; + nat = label_nat + SPACING + button_nat; + } else { + min = MAX (label_min, button_min); + nat = MAX (label_nat, button_nat); + } + } else { + min = MAX (label_min, button_min); + nat = MAX (label_nat, button_nat); + } + } else { + min = label_min; + nat = label_nat; + } + } else { + if (button_shown) { + min = MAX (label_min + SPACING + button_min, BUTTON_MAX_WIDTH); + nat = MAX (label_nat + SPACING + button_nat, BUTTON_MAX_WIDTH); + } else { + min = label_min; + nat = label_nat; + } + } + + if (minimum) + *minimum = min; + if (natural) + *natural = nat; + if (minimum_baseline) + *minimum_baseline = -1; + if (natural_baseline) + *natural_baseline = -1; +} + +static void +allocate_content (GtkWidget *widget, + int width, + int height, + int baseline) +{ + AdwBanner *self = ADW_BANNER (gtk_widget_get_ancestor (widget, ADW_TYPE_BANNER)); + gboolean button_shown = gtk_widget_is_visible (GTK_WIDGET (self->button)); + int button_width, button_height; + int button_x, button_y; + int label_width_min, label_width, label_height; + int label_x, label_y; + int avail; + gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; + + gtk_widget_measure (GTK_WIDGET (self->title), GTK_ORIENTATION_HORIZONTAL, + -1, &label_width_min, &label_width, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), GTK_ORIENTATION_HORIZONTAL, + -1, &button_width, NULL, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->title), GTK_ORIENTATION_VERTICAL, + width, NULL, &label_height, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), GTK_ORIENTATION_VERTICAL, + width, &button_height, NULL, + NULL, NULL); + + label_width = MIN (label_width, width); + label_x = (width / 2) - (label_width / 2); + label_y = (height / 2) - (label_height / 2); + button_x = is_rtl ? 0 : width - button_width; + button_y = (height / 2) - (button_height / 2); + + avail = (width - label_width) / 2; + if (avail <= button_width + SPACING && button_shown) { + label_x = is_rtl ? (width - label_width - SPACING) : SPACING; + + avail = (width - label_width); + if (avail <= button_width + SPACING) { + button_width = CLAMP (button_width, BUTTON_MAX_WIDTH, width); + label_x = (width - label_width) / 2; + label_y = 0; + button_x = (width / 2) - (button_width / 2); + button_y = height - button_height; + } + } + + gtk_widget_allocate (GTK_WIDGET (self->title), + label_width, label_height, -1, + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (label_x, label_y))); + + gtk_widget_allocate (GTK_WIDGET (self->button), + button_width, button_height, -1, + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (button_x, button_y))); +} + +static GtkSizeRequestMode +get_content_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +adw_banner_class_init (AdwBannerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = adw_banner_get_property; + object_class->set_property = adw_banner_set_property; + object_class->dispose = adw_banner_dispose; + + /** + * AdwBanner:title: (attributes org.gtk.Property.get=adw_banner_get_title org.gtk.Property.set=adw_banner_set_title) + * + * The title for this banner. + * + * See also: [property@Banner:use-markup]. + * + * Since: 1.3 + */ + props[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner:button-label: (attributes org.gtk.Property.get=adw_banner_get_button_label org.gtk.Property.set=adw_banner_set_button_label) + * + * The label to show on the button. + * + * If set to `""` or `NULL`, the button won't be shown. + * + * The button can be used with a `GAction`, or with the + * [signal@Banner::button-clicked] signal. + * + * Since: 1.3 + */ + props[PROP_BUTTON_LABEL] = + g_param_spec_string ("button-label", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner:use-markup: (attributes org.gtk.Property.get=adw_banner_get_use_markup org.gtk.Property.set=adw_banner_set_use_markup) + * + * Whether to use Pango markup for the banner title. + * + * See also [func@Pango.parse_markup]. + * + * Since: 1.3 + */ + props[PROP_USE_MARKUP] = + g_param_spec_boolean ("use-markup", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner:revealed: (attributes org.gtk.Property.get=adw_banner_get_revealed org.gtk.Property.set=adw_banner_set_revealed) + * + * Whether the banner is currently revealed. + * + * Since: 1.3 + */ + props[PROP_REVEALED] = + g_param_spec_boolean ("revealed", NULL, NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner::button-clicked: + * + * This signal is emitted after the action button has been clicked. + * + * It can be used as an alternative to setting an action. + * + * Since: 1.3 + */ + signals[SIGNAL_BUTTON_CLICKED] = + g_signal_new ("button-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + g_object_class_install_properties (object_class, LAST_PROP, props); + g_object_class_override_property (object_class, PROP_ACTION_NAME, "action-name"); + g_object_class_override_property (object_class, PROP_ACTION_TARGET, "action-target"); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Adwaita/ui/adw-banner.ui"); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, gizmo); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, title); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, revealer); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, button); + + gtk_widget_class_bind_template_callback (widget_class, button_clicked); + + gtk_widget_class_set_css_name (widget_class, "banner"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP); + + g_type_ensure (ADW_TYPE_GIZMO); +} + +static const char * +adw_banner_get_action_name (GtkActionable *actionable) +{ + AdwBanner *self = ADW_BANNER (actionable); + + return gtk_actionable_get_action_name (GTK_ACTIONABLE (self->button)); +} + +static void +adw_banner_set_action_name (GtkActionable *actionable, + const char *action_name) +{ + AdwBanner *self = ADW_BANNER (actionable); + + gtk_actionable_set_action_name (GTK_ACTIONABLE (self->button), action_name); +} + +static GVariant * +adw_banner_get_action_target_value (GtkActionable *actionable) +{ + AdwBanner *self = ADW_BANNER (actionable); + + return gtk_actionable_get_action_target_value (GTK_ACTIONABLE (self->button)); +} + +static void +adw_banner_set_action_target_value (GtkActionable *actionable, + GVariant *action_target) +{ + AdwBanner *self = ADW_BANNER (actionable); + + gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self->button), action_target); +} + +static void +adw_banner_actionable_init (GtkActionableInterface *iface) +{ + iface->get_action_name = adw_banner_get_action_name; + iface->set_action_name = adw_banner_set_action_name; + iface->get_action_target_value = adw_banner_get_action_target_value; + iface->set_action_target_value = adw_banner_set_action_target_value; +} + +static void +adw_banner_init (AdwBanner *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + gtk_widget_set_layout_manager (GTK_WIDGET (self->gizmo), gtk_custom_layout_new (get_content_request_mode, + measure_content, + allocate_content)); +} + +/** + * adw_banner_new: + * @title: the banner title + * + * Creates a new `AdwBanner`. + * + * Returns: the newly created `AdwBanner` + * + * Since: 1.3 + */ +GtkWidget * +adw_banner_new (const char *title) +{ + g_return_val_if_fail (title != NULL, NULL); + + return g_object_new (ADW_TYPE_BANNER, + "title", title, + NULL); +} + +/** + * adw_banner_get_title: (attributes org.gtk.Method.get_property=title) + * @self: a banner + * + * Gets the title for @self. + * + * Returns: the title for @self + * + * Since: 1.3 + */ +const char * +adw_banner_get_title (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), NULL); + + return gtk_label_get_label (self->title); +} + +/** + * adw_banner_set_title: (attributes org.gtk.Method.set_property=title) + * @self: a banner + * @title: the title + * + * Sets the title for this banner. + * + * See also: [property@Banner:use-markup]. + * + * Since: 1.3 + */ +void +adw_banner_set_title (AdwBanner *self, + const char *title) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + g_return_if_fail (title != NULL); + + if (g_strcmp0 (gtk_label_get_label (self->title), title) == 0) + return; + + gtk_label_set_label (self->title, title); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); +} + +/** + * adw_banner_get_button_label: (attributes org.gtk.Method.get_property=button-label) + * @self: a banner + * + * Gets the button label for @self. + * + * Returns: (nullable): the button label for @self + * + * Since: 1.3 + */ +const char * +adw_banner_get_button_label (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), NULL); + + return gtk_button_get_label (self->button); +} + +/** + * adw_banner_set_button_label: (attributes org.gtk.Method.set_property=button-label) + * @self: a banner + * @label: (nullable): the label + * + * Sets the button label for @self. + * + * If set to `""` or `NULL`, the button won't be shown. + * + * The button can be used with a `GAction`, or with the + * [signal@Banner::button-clicked] signal. + * + * Since: 1.3 + */ +void +adw_banner_set_button_label (AdwBanner *self, + const char *label) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + + if (g_strcmp0 (gtk_button_get_label (self->button), label) == 0) + return; + + gtk_widget_set_visible (GTK_WIDGET (self->button), label && label[0]); + + gtk_button_set_label (self->button, label); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUTTON_LABEL]); +} + +/** + * adw_banner_get_use_markup: (attributes org.gtk.Method.get_property=use-markup) + * @self: a banner + * + * Gets whether to use Pango markup for the banner title. + * + * Returns: whether to use markup + * + * Since: 1.3 + */ +gboolean +adw_banner_get_use_markup (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), FALSE); + + return gtk_label_get_use_markup (self->title); +} + +/** + * adw_banner_set_use_markup: (attributes org.gtk.Method.set_property=use-markup) + * @self: a banner + * @use_markup: whether to use markup + * + * Sets whether to use Pango markup for the banner title. + * + * See also [func@Pango.parse_markup]. + * + * Since: 1.3 + */ +void +adw_banner_set_use_markup (AdwBanner *self, + gboolean use_markup) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + + use_markup = !!use_markup; + + if (gtk_label_get_use_markup (self->title) == use_markup) + return; + + gtk_label_set_use_markup (self->title, use_markup); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_MARKUP]); +} + +/** + * adw_banner_get_revealed: (attributes org.gtk.Method.get_property=revealed) + * @self: a banner + * + * Gets if a banner is revealed + * + * Returns: Whether a banner is revealed + * + * Since: 1.3 + */ +gboolean +adw_banner_get_revealed (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), FALSE); + + return gtk_revealer_get_reveal_child (GTK_REVEALER (self->revealer)); +} + +/** + * adw_banner_set_revealed: (attributes org.gtk.Method.get_property=revealed) + * @self: a banner + * @revealed: whether a banner should be revealed + * + * Sets whether a banner should be revealed + * + * Since: 1.3 + */ +void +adw_banner_set_revealed (AdwBanner *self, + gboolean revealed) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + + revealed = !!revealed; + + if (gtk_revealer_get_reveal_child (GTK_REVEALER (self->revealer)) == revealed) + return; + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), revealed); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEALED]); +} diff --git a/src/adw-banner.h b/src/adw-banner.h new file mode 100644 index 000000000..57525a2cd --- /dev/null +++ b/src/adw-banner.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 Jamie Murphy + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION) +#error "Only can be included directly." +#endif + +#include "adw-version.h" + +#include +#include "adw-enums.h" + +G_BEGIN_DECLS + +#define ADW_TYPE_BANNER (adw_banner_get_type()) + +ADW_AVAILABLE_IN_1_3 +G_DECLARE_FINAL_TYPE (AdwBanner, adw_banner, ADW, BANNER, GtkWidget) + +ADW_AVAILABLE_IN_1_3 +GtkWidget *adw_banner_new (const char *title) G_GNUC_WARN_UNUSED_RESULT; + +ADW_AVAILABLE_IN_1_3 +const char *adw_banner_get_title (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_title (AdwBanner *self, + const char *title); + +ADW_AVAILABLE_IN_1_3 +const char *adw_banner_get_button_label (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_button_label (AdwBanner *self, + const char *label); + +ADW_AVAILABLE_IN_1_3 +gboolean adw_banner_get_revealed (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_revealed (AdwBanner *self, + gboolean revealed); + +ADW_AVAILABLE_IN_1_3 +gboolean adw_banner_get_use_markup (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_use_markup (AdwBanner *self, + gboolean use_markup); + +G_END_DECLS diff --git a/src/adw-banner.ui b/src/adw-banner.ui new file mode 100644 index 000000000..04e7c615a --- /dev/null +++ b/src/adw-banner.ui @@ -0,0 +1,42 @@ + + + + + diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml index 159fc0e72..4169e278f 100644 --- a/src/adwaita.gresources.xml +++ b/src/adwaita.gresources.xml @@ -16,6 +16,7 @@ adw-about-window.ui adw-action-row.ui + adw-banner.ui adw-combo-row.ui adw-entry-row.ui adw-expander-row.ui diff --git a/src/adwaita.h b/src/adwaita.h index b70b2f29d..4524b8b27 100644 --- a/src/adwaita.h +++ b/src/adwaita.h @@ -29,6 +29,7 @@ G_BEGIN_DECLS #include "adw-application.h" #include "adw-application-window.h" #include "adw-avatar.h" +#include "adw-banner.h" #include "adw-bin.h" #include "adw-button-content.h" #include "adw-carousel.h" diff --git a/src/meson.build b/src/meson.build index 4701a2cd4..1c8e72344 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,6 +11,7 @@ libadwaita_resources = gnome.compile_resources( adw_public_enum_headers = [ 'adw-animation.h', + 'adw-banner.h', 'adw-flap.h', 'adw-fold-threshold-policy.h', 'adw-easing.h', @@ -90,6 +91,7 @@ src_headers = [ 'adw-application.h', 'adw-application-window.h', 'adw-avatar.h', + 'adw-banner.h', 'adw-bin.h', 'adw-button-content.h', 'adw-carousel.h', @@ -156,6 +158,7 @@ src_sources = [ 'adw-application.c', 'adw-application-window.c', 'adw-avatar.c', + 'adw-banner.c', 'adw-bin.c', 'adw-button-content.c', 'adw-carousel.c', diff --git a/src/stylesheet/widgets/_misc.scss b/src/stylesheet/widgets/_misc.scss index d3c30ee22..8d4465f91 100644 --- a/src/stylesheet/widgets/_misc.scss +++ b/src/stylesheet/widgets/_misc.scss @@ -1,3 +1,15 @@ +/*********** + * Banners * + ***********/ +banner { + background-color: gtkmix($accent_bg_color, $window_bg_color, 30%); + color: $window_fg_color; + + > revealer > widget { + padding: 6px; + } +} + /********** * Frames * **********/ diff --git a/tests/meson.build b/tests/meson.build index 393719ee9..f1dbfe9b9 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -32,6 +32,7 @@ test_names = [ 'test-animation-target', 'test-application-window', 'test-avatar', + 'test-banner', 'test-bin', 'test-button-content', 'test-carousel', diff --git a/tests/test-banner.c b/tests/test-banner.c new file mode 100644 index 000000000..53676c847 --- /dev/null +++ b/tests/test-banner.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 Jamie Murphy + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +static void +test_adw_banner_revealed (void) +{ + AdwBanner *banner = g_object_ref_sink (ADW_BANNER (adw_banner_new (""))); + + g_assert_nonnull (banner); + + g_assert_false (adw_banner_get_revealed (banner)); + + adw_banner_set_revealed (banner, TRUE); + g_assert_true (adw_banner_get_revealed (banner)); + + adw_banner_set_revealed (banner, FALSE); + g_assert_false (adw_banner_get_revealed (banner)); + + g_assert_finalize_object (banner); +} + +static void +test_adw_banner_title (void) +{ + AdwBanner *banner = g_object_ref_sink (ADW_BANNER (adw_banner_new (""))); + + g_assert_nonnull (banner); + + g_assert_cmpstr (adw_banner_get_title (banner), ==, ""); + + adw_banner_set_title (banner, "Dummy title"); + g_assert_cmpstr (adw_banner_get_title (banner), ==, "Dummy title"); + + adw_banner_set_use_markup (banner, FALSE); + adw_banner_set_title (banner, "Invalid markup"); + g_assert_cmpstr (adw_banner_get_title (banner), ==, "Invalid markup"); + + g_assert_finalize_object (banner); +} + +static void +test_adw_banner_button_label (void) +{ + AdwBanner *banner = g_object_ref_sink (ADW_BANNER (adw_banner_new (""))); + char *button_label; + + g_assert_nonnull (banner); + + g_object_get (banner, "button-label", &button_label, NULL); + g_assert_null (button_label); + + adw_banner_set_button_label (banner, "Dummy label"); + g_assert_cmpstr (adw_banner_get_button_label (banner), ==, "Dummy label"); + + adw_banner_set_button_label (banner, NULL); + g_assert_cmpstr (adw_banner_get_button_label (banner), ==, ""); + + g_object_set (banner, "button-label", "Button 2", NULL); + g_assert_cmpstr (adw_banner_get_button_label (banner), ==, "Button 2"); + + g_assert_finalize_object (banner); +} + +int +main (int argc, + char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + adw_init (); + + g_test_add_func ("/Adwaita/Banner/revealed", test_adw_banner_revealed); + g_test_add_func ("/Adwaita/Banner/title", test_adw_banner_title); + g_test_add_func ("/Adwaita/Banner/button_label", test_adw_banner_button_label); + + return g_test_run (); +} -- GitLab