diff --git a/examples/miscellaneous/opacitywindow.py b/examples/miscellaneous/opacitywindow.py new file mode 100644 index 000000000..690c7a0aa --- /dev/null +++ b/examples/miscellaneous/opacitywindow.py @@ -0,0 +1,28 @@ +from kivy.app import App +from kivy.lang import Builder + +kv = ''' +#:import window kivy.core.window.Window + +BoxLayout: + orientation: 'vertical' + Label: + text: f'Window opacity: {window.opacity}' + font_size: '25sp' + Slider: + size_hint_y: 4 + min: 0.0 + max: 1.0 + value: window.opacity + on_value: window.opacity = args[1] +''' + + +class WindowOpacityApp(App): + + def build(self): + return Builder.load_string(kv) + + +if __name__ == '__main__': + WindowOpacityApp().run() diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index e83c97827..3627d1d89 100644 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -933,6 +933,33 @@ class WindowBase(EventDispatcher): the position set in :class:`~kivy.config.Config`. ''' + def _get_opacity(self): + return self._get_window_opacity() + + def _set_opacity(self, opacity): + return self._set_window_opacity(opacity) + + def _get_window_opacity(self): + Logger.warning('Window: Opacity is not implemented in the current ' + 'window provider') + + def _set_window_opacity(self, opacity): + Logger.warning('Window: Opacity is not implemented in the current ' + 'window provider') + + opacity = AliasProperty(_get_opacity, _set_opacity, cache=True) + '''Opacity of the window. Accepts a value between 0.0 (transparent) and + 1.0 (opaque). + + .. note:: + This feature requires the SDL2 window provider. + + .. versionadded:: 2.3.0 + + :attr:`opacity` is an :class:`~kivy.properties.AliasProperty` and defaults + to `1.0`. + ''' + @property def __self__(self): return self diff --git a/kivy/core/window/_window_sdl2.pyx b/kivy/core/window/_window_sdl2.pyx index de797b82f..08cafd9cf 100644 --- a/kivy/core/window/_window_sdl2.pyx +++ b/kivy/core/window/_window_sdl2.pyx @@ -394,6 +394,23 @@ cdef class _WindowSDL2Storage: def set_window_pos(self, x, y): SDL_SetWindowPosition(self.win, x, y) + def set_window_opacity(self, opacity): + if SDL_SetWindowOpacity(self.win, opacity): + message = (SDL_GetError()).decode('utf-8', 'replace') + Logger.error(f'WindowSDL: Setting opacity to {opacity} failed - ' + f'{message}') + return False + return True + + def get_window_opacity(self): + cdef float opacity + if SDL_GetWindowOpacity(self.win, &opacity): + message = (SDL_GetError()).decode('utf-8', 'replace') + Logger.error(f'WindowSDL: Getting opacity failed - {message}') + return 1.0 + else: + return opacity + def get_window_info(self): cdef SDL_SysWMinfo wm_info SDL_GetVersion(&wm_info.version) diff --git a/kivy/core/window/window_sdl2.py b/kivy/core/window/window_sdl2.py index 028f00c59..619e4919b 100644 --- a/kivy/core/window/window_sdl2.py +++ b/kivy/core/window/window_sdl2.py @@ -482,6 +482,13 @@ class WindowSDL(WindowBase): def _set_window_pos(self, x, y): self._win.set_window_pos(x, y) + def _get_window_opacity(self): + return self._win.get_window_opacity() + + def _set_window_opacity(self, opacity): + if self.opacity != opacity: + return self._win.set_window_opacity(opacity) + # Transparent Window background def _is_shaped(self): return self._win.is_window_shaped() diff --git a/kivy/lib/sdl2.pxi b/kivy/lib/sdl2.pxi index c819c654c..5ffc8885f 100644 --- a/kivy/lib/sdl2.pxi +++ b/kivy/lib/sdl2.pxi @@ -924,6 +924,10 @@ cdef extern from "SDL_audio.h": ) cdef int SDL_ConvertAudio(SDL_AudioCVT *cvt) +cdef extern from "SDL_video.h": + cdef int SDL_SetWindowOpacity(SDL_Window *window, float opacity) + cdef int SDL_GetWindowOpacity(SDL_Window *window, float *opacity) + cdef extern from "SDL_mixer.h": cdef struct Mix_Chunk: int allocated diff --git a/kivy/tests/test_window_base.py b/kivy/tests/test_window_base.py index d99ebdce8..79620f365 100644 --- a/kivy/tests/test_window_base.py +++ b/kivy/tests/test_window_base.py @@ -1,6 +1,7 @@ from itertools import product from kivy.tests import GraphicUnitTest +from kivy.logger import LoggerHistory class WindowBaseTest(GraphicUnitTest): @@ -18,3 +19,42 @@ class WindowBaseTest(GraphicUnitTest): assert result_sy == expected_sy finally: win.system_size = old_system_size + + +class WindowOpacityTest(GraphicUnitTest): + + def setUp(self): + super().setUp() + self._prev_window_opacity = self.Window.opacity + self._prev_history = LoggerHistory.history[:] + + def tearDown(self): + self.Window.opacity = self._prev_window_opacity + LoggerHistory.history[:] = self._prev_history + super().tearDown() + + def get_new_opacity_value(self): + opacity = self.Window.opacity + opacity = opacity - 0.1 if opacity >= 0.9 else opacity + 0.1 + return round(opacity, 2) + + def check_opacity_support(self): + LoggerHistory.clear_history() + self.Window.opacity = self.get_new_opacity_value() + return not LoggerHistory.history + + def test_window_opacity_property(self): + if self.check_opacity_support(): + opacity = self.get_new_opacity_value() + self.Window.opacity = opacity + self.assertEqual(self.Window.opacity, opacity) + + def test_window_opacity_clamping_positive(self): + if self.check_opacity_support(): + self.Window.opacity = 1.5 + self.assertEqual(self.Window.opacity, 1.0) + + def test_window_opacity_clamping_negative(self): + if self.check_opacity_support(): + self.Window.opacity = -1.5 + self.assertEqual(self.Window.opacity, 0.0)