diff --git a/packages/flet/lib/src/controls/bottom_app_bar.dart b/packages/flet/lib/src/controls/bottom_app_bar.dart index 0753b2e8e7..37ce8de403 100644 --- a/packages/flet/lib/src/controls/bottom_app_bar.dart +++ b/packages/flet/lib/src/controls/bottom_app_bar.dart @@ -10,18 +10,35 @@ class BottomAppBarControl extends StatelessWidget { Widget build(BuildContext context) { debugPrint("BottomAppBarControl build: ${control.id}"); - var bottomAppBar = BottomAppBar( - clipBehavior: control.getClipBehavior("clip_behavior", Clip.none)!, + final theme = Theme.of(context); + var borderRadius = control.getBorderRadius("border_radius"); + final clipBehavior = control.getClipBehavior( + "clip_behavior", + borderRadius != null && borderRadius != BorderRadius.zero + ? Clip.antiAlias + : Clip.none)!; + + Widget bottomAppBar = BottomAppBar( + clipBehavior: clipBehavior, padding: control.getPadding("padding"), height: control.getDouble("height"), elevation: control.getDouble("elevation"), - shape: control.getNotchedShape("shape", Theme.of(context)), + shape: control.getNotchedShape("shape", theme), shadowColor: control.getColor("shadow_color", context), color: control.getColor("bgcolor", context), notchMargin: control.getDouble("notch_margin", 4.0)!, child: control.buildWidget("content"), ); + if (borderRadius != null && borderRadius != BorderRadius.zero) { + bottomAppBar = ClipRRect( + borderRadius: borderRadius, + // can't use Clip.none here, so fallback to Clip.antiAlias in that case + clipBehavior: clipBehavior == Clip.none ? Clip.antiAlias : clipBehavior, + child: bottomAppBar, + ); + } + return LayoutControl(control: control, child: bottomAppBar); } } diff --git a/sdk/python/examples/controls/bottom_app_bar/__init__.py b/sdk/python/examples/controls/bottom_app_bar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdk/python/examples/controls/bottom_app_bar/border_radius.py b/sdk/python/examples/controls/bottom_app_bar/border_radius.py new file mode 100644 index 0000000000..73ba65b856 --- /dev/null +++ b/sdk/python/examples/controls/bottom_app_bar/border_radius.py @@ -0,0 +1,25 @@ +import flet as ft + + +def main(page: ft.Page): + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + + page.bottom_appbar = ft.BottomAppBar( + border_radius=ft.BorderRadius.all(20), + bgcolor=ft.Colors.BLUE, + content=ft.Row( + controls=[ + ft.IconButton(icon=ft.Icons.MENU, icon_color=ft.Colors.WHITE), + ft.Container(expand=True), + ft.IconButton(icon=ft.Icons.SEARCH, icon_color=ft.Colors.WHITE), + ft.IconButton(icon=ft.Icons.FAVORITE, icon_color=ft.Colors.WHITE), + ] + ), + ) + + page.add(ft.Text("Content goes here...")) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/controls/bottom_app_bar/media/notched_fab.png b/sdk/python/examples/controls/bottom_app_bar/media/notched_fab.png deleted file mode 100644 index 4f40727a44..0000000000 Binary files a/sdk/python/examples/controls/bottom_app_bar/media/notched_fab.png and /dev/null differ diff --git a/sdk/python/examples/controls/bottom_app_bar/notched_fab.py b/sdk/python/examples/controls/bottom_app_bar/notched_fab.py index 1487e1f805..ea1b3dd187 100644 --- a/sdk/python/examples/controls/bottom_app_bar/notched_fab.py +++ b/sdk/python/examples/controls/bottom_app_bar/notched_fab.py @@ -11,12 +11,6 @@ def main(page: ft.Page): ) page.floating_action_button_location = ft.FloatingActionButtonLocation.CENTER_DOCKED - page.appbar = ft.AppBar( - title=ft.Text("Bottom AppBar Demo"), - center_title=True, - bgcolor=ft.Colors.GREEN_300, - automatically_imply_leading=False, - ) page.bottom_appbar = ft.BottomAppBar( bgcolor=ft.Colors.BLUE, shape=ft.CircularRectangleNotchShape(), @@ -30,7 +24,8 @@ def main(page: ft.Page): ), ) - page.add(ft.Text("Body!")) + page.add(ft.Text("Content goes here...")) -ft.run(main) +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/packages/flet/docs/controls/bottomappbar.md b/sdk/python/packages/flet/docs/controls/bottomappbar.md index fde71b8b88..e2b500afe5 100644 --- a/sdk/python/packages/flet/docs/controls/bottomappbar.md +++ b/sdk/python/packages/flet/docs/controls/bottomappbar.md @@ -2,10 +2,9 @@ class_name: flet.BottomAppBar examples: ../../examples/controls/bottom_app_bar example_images: ../test-images/examples/material/golden/macos/bottom_app_bar -example_media: ../examples/controls/bottom_app_bar/media --- -{{ class_summary(class_name, example_images + "/image_for_docs.png", image_caption="Basic BottomAppBar", image_width="100%") }} +{{ class_summary(class_name, example_images + "/image_for_docs.png", image_caption="Basic BottomAppBar", image_width="50%") }} ## Examples @@ -17,7 +16,14 @@ example_media: ../examples/controls/bottom_app_bar/media --8<-- "{{ examples }}/notched_fab.py" ``` -{{ image(example_media + "/notched_fab.png", alt="notched-fab", width="80%") }} +{{ image(example_images + "/notched_fab.png", width="80%") }} +### Custom border radius + +```python +--8<-- "{{ examples }}/border_radius.py" +``` + +{{ image(example_images + "/border_radius.png", width="80%") }} {{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/border_radius.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/border_radius.png new file mode 100644 index 0000000000..dcf9fa8b42 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/border_radius.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/image_for_docs.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/image_for_docs.png index c3bc4fac62..b3888ad796 100644 Binary files a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/image_for_docs.png and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/image_for_docs.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/notched_fab.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/notched_fab.png new file mode 100644 index 0000000000..bc1e544335 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/bottom_app_bar/notched_fab.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_app_bar.py b/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_app_bar.py index e9c5dc7c41..f125884452 100644 --- a/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_app_bar.py +++ b/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_app_bar.py @@ -2,22 +2,69 @@ import flet as ft import flet.testing as ftt +from examples.controls.bottom_app_bar import border_radius, notched_fab @pytest.mark.asyncio(loop_scope="function") async def test_image_for_docs(flet_app_function: ftt.FletTestApp, request): flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT + flet_app_function.page.window.width = 400 + await flet_app_function.assert_control_screenshot( request.node.name, ft.BottomAppBar( bgcolor=ft.Colors.SURFACE_CONTAINER_LOW, content=ft.Row( + alignment=ft.MainAxisAlignment.SPACE_AROUND, controls=[ ft.IconButton(ft.Icons.MENU), ft.IconButton(ft.Icons.SEARCH), ft.IconButton(ft.Icons.SETTINGS), ], - alignment=ft.MainAxisAlignment.SPACE_AROUND, ), ), ) + + +@pytest.mark.parametrize( + "flet_app_function", + [{"flet_app_main": border_radius.main}], + indirect=True, +) +@pytest.mark.asyncio(loop_scope="function") +async def test_border_radius(flet_app_function: ftt.FletTestApp): + flet_app_function.page.enable_screenshots = True + flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT + flet_app_function.page.window.width = 500 + flet_app_function.page.window.height = 400 + flet_app_function.page.update() + + await flet_app_function.tester.pump_and_settle() + flet_app_function.assert_screenshot( + "border_radius", + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) + + +@pytest.mark.parametrize( + "flet_app_function", + [{"flet_app_main": notched_fab.main}], + indirect=True, +) +@pytest.mark.asyncio(loop_scope="function") +async def test_notched_fab(flet_app_function: ftt.FletTestApp): + flet_app_function.page.enable_screenshots = True + flet_app_function.page.theme_mode = ft.ThemeMode.LIGHT + flet_app_function.page.window.width = 500 + flet_app_function.page.window.height = 400 + flet_app_function.page.update() + + await flet_app_function.tester.pump_and_settle() + flet_app_function.assert_screenshot( + "notched_fab", + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) diff --git a/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_sheet.py b/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_sheet.py index f21e252a0e..ccb5755ecf 100644 --- a/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_sheet.py +++ b/sdk/python/packages/flet/integration_tests/examples/material/test_bottom_sheet.py @@ -25,7 +25,7 @@ async def test_image_for_docs(flet_app_function: ftt.FletTestApp, request): flet_app_function.page.update() await flet_app_function.tester.pump_and_settle() flet_app_function.assert_screenshot( - "test_image_for_docs", + request.node.name, await flet_app_function.page.take_screenshot( pixel_ratio=flet_app_function.screenshots_pixel_ratio ), diff --git a/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py b/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py index 41c4d8fa9e..10aea124d1 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py +++ b/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py @@ -1,6 +1,7 @@ from typing import Optional from flet.controls.base_control import control +from flet.controls.border_radius import BorderRadiusValue from flet.controls.control import Control from flet.controls.layout_control import LayoutControl from flet.controls.padding import PaddingValue @@ -23,12 +24,12 @@ class BottomAppBar(LayoutControl): ft.BottomAppBar( bgcolor=ft.Colors.SURFACE_CONTAINER_LOW, content=ft.Row( + alignment=ft.MainAxisAlignment.SPACE_AROUND, controls=[ ft.IconButton(ft.Icons.MENU), ft.IconButton(ft.Icons.SEARCH), ft.IconButton(ft.Icons.SETTINGS), ], - alignment=ft.MainAxisAlignment.SPACE_AROUND, ), ) ``` @@ -43,31 +44,43 @@ class BottomAppBar(LayoutControl): """ The fill color to use for this app bar. - Defaults to [`BottomAppBarTheme.bgcolor`][flet.], or if - that is `None`, falls back to [`ColorScheme.surface`][flet.]. + If `None`, [`BottomAppBarTheme.bgcolor`][flet.] is used; + if that is also `None`, then defaults to [`ColorScheme.surface`][flet.]. """ shadow_color: Optional[ColorValue] = None """ The color of the shadow below this app bar. + + If `None`, [`BottomAppBarTheme.shadow_color`][flet.] is used; + if that is also `None`, then defaults to [`Colors.TRANSPARENT`][flet.]. """ padding: Optional[PaddingValue] = None """ Empty space to inscribe inside a container decoration (background, border). - Defaults to [`BottomAppBarTheme.padding`][flet.], or if - that is `None`, falls back to `Padding.symmetric(vertical=12.0, horizontal=16.0)`. + If `None`, [`BottomAppBarTheme.padding`][flet.] is used; + if that is also `None`, then defaults to + `Padding.symmetric(vertical=12.0, horizontal=16.0)`. """ - clip_behavior: ClipBehavior = ClipBehavior.NONE + clip_behavior: Optional[ClipBehavior] = None """ Defines how the [`content`][(c).] of this app bar should be clipped. + + If `None`, defaults to: + - [`ClipBehavior.ANTI_ALIAS`][flet.] if [`border_radius`][(c).] + is set and not equal to [`BorderRadius.all(0)`][flet.BorderRadius.all]; + - Else [`ClipBehavior.NONE`][flet.]. """ shape: Optional[NotchShape] = None """ The notch that is made for the floating action button. + + If `None`, [`BottomAppBarTheme.shape`][flet.] is used; + if that is also `None`, then the shape will be rectangular with no notch. """ notch_margin: Number = 4.0 @@ -75,15 +88,25 @@ class BottomAppBar(LayoutControl): The margin between the [`FloatingActionButton`][flet.] and this app bar's notch. - Can be visible only if [`shape`][(c).] is `None`. + Note: + Has effect only if [`shape`][(c).] is not `None`. """ elevation: Optional[Number] = None """ - This property controls the size of the shadow below this app bar. + The z-coordinate at which to place this bottom app bar relative to its + parent. It controls the size of the shadow below this app bar. + + If `None`, [`BottomAppBarTheme.elevation`][flet.] is used; + if that is also `None`, then defaults to `3`. Raises: - ValueError: If [`elevation`][(c).] is negative. + ValueError: If it is less than `0`. + """ + + border_radius: Optional[BorderRadiusValue] = None + """ + The border radius to apply when clipping and painting this app bar. """ def before_update(self): diff --git a/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py b/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py index e8d83adeb1..6500bfd1a8 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py +++ b/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py @@ -36,7 +36,7 @@ class FloatingActionButton(LayoutControl): The content of this button. Raises: - ValueError: If neither [`icon`][(c).] nor a valid [`content`][(c).] + ValueError: If neither [`icon`][(c).] nor a valid `content` (string or visible Control) is provided. """ @@ -95,7 +95,7 @@ class FloatingActionButton(LayoutControl): Defaults to `6`. Raises: - ValueError: If [`elevation`][(c).] is negative. + ValueError: If it is less than `0`. """ disabled_elevation: Optional[Number] = None @@ -105,7 +105,7 @@ class FloatingActionButton(LayoutControl): Defaults to the same value as `elevation`. Raises: - ValueError: If [`disabled_elevation`][(c).] is negative. + ValueError: If it is less than `0`. """ focus_elevation: Optional[Number] = None @@ -115,7 +115,7 @@ class FloatingActionButton(LayoutControl): Defaults to `8`. Raises: - ValueError: If [`focus_elevation`][(c).] is negative. + ValueError: If it is less than `0`. """ highlight_elevation: Optional[Number] = None @@ -125,7 +125,7 @@ class FloatingActionButton(LayoutControl): Defaults to `12`. Raises: - ValueError: If [`highlight_elevation`][(c).] is negative. + ValueError: If it is less than `0`. """ hover_elevation: Optional[Number] = None @@ -135,7 +135,7 @@ class FloatingActionButton(LayoutControl): Defaults to `8`. Raises: - ValueError: If [`hover_elevation`][(c).] is negative. + ValueError: If it is less than `0`. """ hover_color: Optional[ColorValue] = None