Files
Kiwy-Signage/src/signage_player.kv
Kiwy Signage Player b2d380511a Refactor: Move UI definitions to KV file and modularize edit popup
- Created src/edit_popup.py module for EditPopup and DrawingLayer classes
- Moved EditPopup UI definition to signage_player.kv (reduced main.py by 533 lines)
- Moved CardSwipePopup UI definition to signage_player.kv (reduced main.py by 41 lines)
- Improved code organization with better separation of concerns
- main.py reduced from 2,384 to 1,811 lines (24% reduction)
- All functionality preserved, no breaking changes
2025-12-14 14:48:35 +02:00

913 lines
29 KiB
Plaintext

#:kivy 2.1.0
# Custom On-Screen Keyboard Widget
<CustomKeyboard@FloatLayout>:
size_hint: None, None
width: root.parent.width * 0.5 if root.parent else dp(600)
height: self.width / 3
pos: (root.parent.width - self.width) / 2 if root.parent else 0, 0
opacity: 0
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 0.95
RoundedRectangle:
size: self.size
pos: self.pos
radius: [dp(15), dp(15), 0, 0]
BoxLayout:
orientation: 'vertical'
padding: dp(5)
spacing: dp(5)
# Close button bar
BoxLayout:
size_hint: 1, None
height: dp(40)
padding: [dp(5), 0]
Widget:
Button:
text: '✕'
size_hint: None, 1
width: dp(40)
background_color: 0.8, 0.2, 0.2, 0.9
font_size: sp(20)
bold: True
on_press: root.parent.hide_keyboard() if root.parent and hasattr(root.parent, 'hide_keyboard') else None
# Number row
BoxLayout:
size_hint: 1, 1
spacing: dp(3)
Button:
text: '1'
on_press: root.parent.key_pressed('1') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '2'
on_press: root.parent.key_pressed('2') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '3'
on_press: root.parent.key_pressed('3') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '4'
on_press: root.parent.key_pressed('4') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '5'
on_press: root.parent.key_pressed('5') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '6'
on_press: root.parent.key_pressed('6') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '7'
on_press: root.parent.key_pressed('7') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '8'
on_press: root.parent.key_pressed('8') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '9'
on_press: root.parent.key_pressed('9') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: '0'
on_press: root.parent.key_pressed('0') if root.parent and hasattr(root.parent, 'key_pressed') else None
# Top letter row (QWERTYUIOP)
BoxLayout:
size_hint: 1, 1
spacing: dp(3)
Button:
text: 'Q'
on_press: root.parent.key_pressed('q') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'W'
on_press: root.parent.key_pressed('w') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'E'
on_press: root.parent.key_pressed('e') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'R'
on_press: root.parent.key_pressed('r') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'T'
on_press: root.parent.key_pressed('t') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'Y'
on_press: root.parent.key_pressed('y') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'U'
on_press: root.parent.key_pressed('u') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'I'
on_press: root.parent.key_pressed('i') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'O'
on_press: root.parent.key_pressed('o') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'P'
on_press: root.parent.key_pressed('p') if root.parent and hasattr(root.parent, 'key_pressed') else None
# Middle letter row (ASDFGHJKL)
BoxLayout:
size_hint: 1, 1
spacing: dp(3)
Widget:
size_hint: 0.5, 1
Button:
text: 'A'
on_press: root.parent.key_pressed('a') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'S'
on_press: root.parent.key_pressed('s') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'D'
on_press: root.parent.key_pressed('d') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'F'
on_press: root.parent.key_pressed('f') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'G'
on_press: root.parent.key_pressed('g') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'H'
on_press: root.parent.key_pressed('h') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'J'
on_press: root.parent.key_pressed('j') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'K'
on_press: root.parent.key_pressed('k') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'L'
on_press: root.parent.key_pressed('l') if root.parent and hasattr(root.parent, 'key_pressed') else None
Widget:
size_hint: 0.5, 1
# Bottom letter row (ZXCVBNM)
BoxLayout:
size_hint: 1, 1
spacing: dp(3)
Widget:
size_hint: 1, 1
Button:
text: 'Z'
on_press: root.parent.key_pressed('z') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'X'
on_press: root.parent.key_pressed('x') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'C'
on_press: root.parent.key_pressed('c') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'V'
on_press: root.parent.key_pressed('v') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'B'
on_press: root.parent.key_pressed('b') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'N'
on_press: root.parent.key_pressed('n') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'M'
on_press: root.parent.key_pressed('m') if root.parent and hasattr(root.parent, 'key_pressed') else None
Widget:
size_hint: 1, 1
# Bottom row (Space, Backspace)
BoxLayout:
size_hint: 1, 1
spacing: dp(3)
Button:
text: '←'
size_hint: 0.3, 1
font_size: sp(24)
on_press: root.parent.key_pressed('backspace') if root.parent and hasattr(root.parent, 'key_pressed') else None
Button:
text: 'Space'
size_hint: 0.7, 1
on_press: root.parent.key_pressed(' ') if root.parent and hasattr(root.parent, 'key_pressed') else None
<SignagePlayer@FloatLayout>:
size: root.screen_width, root.screen_height
canvas.before:
Color:
rgba: 0, 0, 0, 1
Rectangle:
size: self.size
pos: self.pos
# Main content area for images/videos
FloatLayout:
id: content_area
size: root.screen_width, root.screen_height
size_hint: None, None
pos_hint: {'x': 0, 'y': 0}
# Status label overlay (centered)
Label:
id: status_label
text: 'Loading...'
size_hint: 0.5, 0.5
size: dp(600), dp(120)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
color: 1, 1, 1, 1
font_size: sp(18)
text_size: self.size
halign: 'center'
valign: 'middle'
canvas.before:
Color:
rgba: 0, 0, 0, 0.7
RoundedRectangle:
size: self.size
pos: self.pos
radius: [dp(15)]
# New control panel overlay (bottom center, width for 6 buttons, 90% transparent)
BoxLayout:
id: controls_layout
orientation: 'horizontal'
size_hint: None, None
width: dp(370)
height: dp(70)
pos: (root.width - self.width) / 2, dp(10)
opacity: 1
spacing: dp(10)
padding: dp(10)
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 0.9 # 90% transparent
RoundedRectangle:
size: self.size
pos: self.pos
radius: [dp(20)]
Button:
id: backward_btn
size_hint: None, None
size: dp(50), dp(50)
background_normal: root.resources_path + '/backward.png'
background_down: root.resources_path + '/backward.png'
border: (0, 0, 0, 0)
on_press: root.previous_media()
Button:
id: play_pause_btn
size_hint: None, None
size: dp(50), dp(50)
background_normal: root.resources_path + '/pause.png'
background_down: root.resources_path + '/pause.png'
border: (0, 0, 0, 0)
on_press: root.toggle_pause()
Button:
id: edit_btn
size_hint: None, None
size: dp(50), dp(50)
background_normal: root.resources_path + '/pencil.png'
background_down: root.resources_path + '/pencil.png'
border: (0, 0, 0, 0)
on_press: root.show_edit_interface()
Button:
id: settings_btn
size_hint: None, None
size: dp(50), dp(50)
background_normal: root.resources_path + '/settings.png'
background_down: root.resources_path + '/settings.png'
border: (0, 0, 0, 0)
on_press: root.show_settings()
Button:
id: exit_btn
size_hint: None, None
size: dp(50), dp(50)
background_normal: root.resources_path + '/exit.png'
background_down: root.resources_path + '/exit.png'
border: (0, 0, 0, 0)
on_press: root.show_exit_popup()
Button:
id: forward_btn
size_hint: None, None
size: dp(50), dp(50)
background_normal: root.resources_path + '/forward.png'
background_down: root.resources_path + '/forward.png'
border: (0, 0, 0, 0)
on_press: root.next_media()
# Exit password popup
<ExitPasswordPopup@Popup>:
title: 'Exit Application'
size_hint: 0.4, 0.3
auto_dismiss: False
BoxLayout:
orientation: 'vertical'
padding: dp(20)
spacing: dp(15)
Label:
text: 'Enter password to exit:'
size_hint_y: None
height: dp(30)
TextInput:
id: password_input
multiline: False
password: True
font_size: sp(16)
size_hint_y: None
height: dp(40)
write_tab: False
readonly: True
on_focus: root.on_input_focus(self, self.focus)
Label:
id: error_label
text: ''
color: 1, 0, 0, 1
size_hint_y: None
height: dp(30)
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(50)
spacing: dp(20)
Button:
text: 'Exit'
background_color: 0.6, 0.2, 0.2, 1
on_press: root.check_password()
Button:
text: 'Cancel'
background_color: 0.2, 0.6, 0.2, 1
on_press: root.dismiss()
# Settings popup content
<SettingsPopup@Popup>:
title: 'Player Settings'
size_hint: 0.8, 0.8
auto_dismiss: True
BoxLayout:
orientation: 'vertical'
padding: dp(20)
spacing: dp(15)
# Server configuration
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Server IP:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput:
id: server_input
size_hint_x: 0.7
multiline: False
font_size: sp(14)
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
# Screen name
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Screen Name:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput:
id: screen_input
size_hint_x: 0.7
multiline: False
font_size: sp(14)
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
# Quickconnect key
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Quickconnect:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput:
id: quickconnect_input
size_hint_x: 0.7
multiline: False
font_size: sp(14)
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
# Orientation
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Orientation:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput:
id: orientation_input
size_hint_x: 0.7
multiline: False
font_size: sp(14)
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
# Touch
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Touch:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput:
id: touch_input
size_hint_x: 0.7
multiline: False
font_size: sp(14)
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
# Resolution
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Max Resolution:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput:
id: resolution_input
size_hint_x: 0.7
multiline: False
font_size: sp(14)
hint_text: '1920x1080 or auto'
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
# Edit Feature Enable/Disable
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(40)
spacing: dp(10)
Label:
text: 'Enable Edit Feature:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
CheckBox:
id: edit_enabled_checkbox
size_hint_x: None
width: dp(40)
active: True
on_active: root.on_edit_feature_toggle(self.active)
Label:
text: '(Allow editing images on this player)'
size_hint_x: 0.4
font_size: sp(12)
text_size: self.size
halign: 'left'
valign: 'middle'
color: 0.7, 0.7, 0.7, 1
Widget:
size_hint_y: 0.05
# Reset Buttons Section
Label:
text: 'Reset Options:'
size_hint_y: None
height: dp(30)
text_size: self.size
halign: 'left'
valign: 'middle'
bold: True
font_size: sp(16)
# Reset Buttons Row
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(50)
spacing: dp(10)
Button:
id: reset_auth_btn
text: 'Reset Player Auth'
background_color: 0.8, 0.4, 0.2, 1
on_press: root.reset_player_auth()
Button:
id: reset_playlist_btn
text: 'Reset Playlist to v0'
background_color: 0.8, 0.4, 0.2, 1
on_press: root.reset_playlist_version()
Button:
id: restart_player_btn
text: 'Restart Player'
background_color: 0.2, 0.6, 0.8, 1
on_press: root.restart_player()
# Test Connection Button
Button:
id: test_connection_btn
text: 'Test Server Connection'
size_hint_y: None
height: dp(50)
background_color: 0.2, 0.4, 0.8, 1
on_press: root.test_connection()
# Connection Status Label
Label:
id: connection_status
text: 'Click button to test connection'
size_hint_y: None
height: dp(40)
text_size: self.size
halign: 'center'
valign: 'middle'
color: 0.7, 0.7, 0.7, 1
Widget:
size_hint_y: 0.05
# Status information row
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(30)
spacing: dp(10)
Label:
id: playlist_info
text: 'Playlist: N/A'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(12)
Label:
id: media_count_info
text: 'Media: 0'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(12)
Label:
id: status_info
text: 'Status: Idle'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(12)
Widget:
size_hint_y: 0.05
# Action buttons
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(50)
spacing: dp(20)
Button:
text: 'Save & Close'
background_color: 0.2, 0.6, 0.2, 1
on_press: root.save_and_close()
Button:
text: 'Cancel'
background_color: 0.6, 0.2, 0.2, 1
on_press: root.dismiss()
# Card Swipe Popup
<CardSwipePopup>:
title: 'Card Authentication Required'
size_hint: 0.5, 0.4
auto_dismiss: False
separator_height: 2
BoxLayout:
orientation: 'vertical'
padding: dp(20)
spacing: dp(20)
# Card swipe icon
AsyncImage:
id: icon_image
size_hint: 1, 0.4
allow_stretch: True
keep_ratio: True
# Message label
Label:
id: message_label
text: 'Please swipe your card...'
font_size: sp(20)
size_hint: 1, 0.2
# Countdown timer
Label:
id: countdown_label
text: '5'
font_size: sp(48)
color: 0.9, 0.6, 0.2, 1
size_hint: 1, 0.2
# Cancel button
Button:
text: 'Cancel'
size_hint: 1, 0.2
background_color: 0.9, 0.3, 0.2, 1
on_press: root.cancel(self)
# Edit Popup (Drawing on Images)
<EditPopup>:
title: ''
size_hint: 1, 1
auto_dismiss: False
separator_height: 0
FloatLayout:
# Background image (full screen)
Image:
id: image_widget
allow_stretch: True
keep_ratio: True
size_hint: 1, 1
pos_hint: {'x': 0, 'y': 0}
# Drawing layer (will be added programmatically due to custom class)
# Placeholder widget for drawing layer positioning
Widget:
id: drawing_layer_placeholder
size_hint: 1, 1
pos_hint: {'x': 0, 'y': 0}
# Top toolbar
BoxLayout:
id: top_toolbar
orientation: 'horizontal'
size_hint: 1, None
height: dp(56)
pos_hint: {'top': 1, 'x': 0}
spacing: dp(10)
padding: [dp(10), dp(8)]
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 0.5
Rectangle:
size: self.size
pos: self.pos
Widget: # Spacer
Button:
id: undo_btn
text: 'Undo'
font_size: sp(16)
size_hint: None, 1
width: dp(100)
background_normal: ''
background_color: 0.9, 0.6, 0.2, 0.9
Button:
id: clear_btn
text: 'Clear'
font_size: sp(16)
size_hint: None, 1
width: dp(100)
background_normal: ''
background_color: 0.9, 0.3, 0.2, 0.9
Button:
id: save_btn
text: 'Save'
font_size: sp(16)
size_hint: None, 1
width: dp(100)
background_normal: ''
background_color: 0.2, 0.8, 0.2, 0.9
Button:
id: cancel_btn
text: 'Cancel'
font_size: sp(16)
size_hint: None, 1
width: dp(100)
background_normal: ''
background_color: 0.6, 0.2, 0.2, 0.9
Label:
id: countdown_label
text: '5:00'
font_size: sp(20)
size_hint: None, 1
width: dp(80)
color: 1, 1, 1, 1
bold: True
Widget: # Small spacer
size_hint: None, 1
width: dp(10)
# Right sidebar
BoxLayout:
id: right_sidebar
orientation: 'vertical'
size_hint: None, 1
width: dp(56)
pos_hint: {'right': 1, 'y': 0}
spacing: dp(10)
padding: [dp(8), dp(66), dp(8), dp(10)]
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 0.5
Rectangle:
size: self.size
pos: self.pos
# Color section header
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(55)
spacing: dp(2)
Image:
id: color_icon
size_hint_y: None
height: dp(28)
allow_stretch: True
keep_ratio: True
Label:
text: 'Color'
font_size: sp(11)
bold: True
size_hint_y: None
height: dp(25)
# Color buttons
Button:
id: red_btn
text: 'R'
font_size: sp(18)
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 1, 0, 0, 1
Button:
id: blue_btn
text: 'B'
font_size: sp(18)
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 0, 0, 1, 1
Button:
id: green_btn
text: 'G'
font_size: sp(18)
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 0, 1, 0, 1
Button:
id: black_btn
text: 'K'
font_size: sp(18)
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 0, 0, 0, 1
Button:
id: white_btn
text: 'W'
font_size: sp(18)
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 1, 1, 1, 1
# Spacer
Widget:
size_hint_y: 0.2
# Thickness section header
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: dp(55)
spacing: dp(2)
Image:
id: thickness_icon
size_hint_y: None
height: dp(28)
allow_stretch: True
keep_ratio: True
Label:
text: 'Size'
font_size: sp(11)
bold: True
size_hint_y: None
height: dp(25)
# Thickness buttons
Button:
id: small_btn
text: 'S'
font_size: sp(20)
bold: True
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 0.3, 0.3, 0.3, 0.9
Button:
id: medium_btn
text: 'M'
font_size: sp(20)
bold: True
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 0.3, 0.3, 0.3, 0.9
Button:
id: large_btn
text: 'L'
font_size: sp(20)
bold: True
size_hint: 1, None
height: dp(50)
background_normal: ''
background_color: 0.3, 0.3, 0.3, 0.9
Widget: # Bottom spacer