feat: Full-screen scaling system with three display modes
- Implemented comprehensive image/video scaling system - Added fit, fill, and stretch scaling modes - Keyboard shortcuts 1,2,3 for real-time mode switching - Enhanced PIL/Pillow integration for image processing - OpenCV video playback with full-screen scaling - Settings integration for scaling preferences - Fixed touch feedback for control buttons - Thread-safe video frame processing - Perfect aspect ratio calculations for any resolution
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"screen_orientation": "Landscape",
|
||||
"screen_name": "tv-holba1",
|
||||
"screen_name": "tv-terasa",
|
||||
"quickconnect_key": "8887779",
|
||||
"server_ip": "192.168.1.245",
|
||||
"port": "5000",
|
||||
"server_ip": "digi-signage.moto-adv.com",
|
||||
"port": "8880",
|
||||
"screen_w": "1920",
|
||||
"screen_h": "1080",
|
||||
"playlist_version": 5
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
360
tkinter_app/src/virtual_keyboard.py
Normal file
360
tkinter_app/src/virtual_keyboard.py
Normal file
@@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Virtual Keyboard Component for Touch Displays
|
||||
Provides an on-screen keyboard for touch-friendly input
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
class VirtualKeyboard:
|
||||
def __init__(self, parent, target_entry=None, dark_theme=True):
|
||||
self.parent = parent
|
||||
self.target_entry = target_entry
|
||||
self.dark_theme = dark_theme
|
||||
self.keyboard_window = None
|
||||
self.caps_lock = False
|
||||
self.shift_pressed = False
|
||||
|
||||
# Define color schemes
|
||||
if dark_theme:
|
||||
self.colors = {
|
||||
'bg_primary': '#1e2124',
|
||||
'bg_secondary': '#2f3136',
|
||||
'bg_tertiary': '#36393f',
|
||||
'accent': '#7289da',
|
||||
'accent_hover': '#677bc4',
|
||||
'text_primary': '#ffffff',
|
||||
'text_secondary': '#b9bbbe',
|
||||
'key_normal': '#4f545c',
|
||||
'key_hover': '#5865f2',
|
||||
'key_special': '#ed4245',
|
||||
'key_function': '#57f287'
|
||||
}
|
||||
else:
|
||||
self.colors = {
|
||||
'bg_primary': '#ffffff',
|
||||
'bg_secondary': '#f8f9fa',
|
||||
'bg_tertiary': '#e9ecef',
|
||||
'accent': '#0d6efd',
|
||||
'accent_hover': '#0b5ed7',
|
||||
'text_primary': '#000000',
|
||||
'text_secondary': '#6c757d',
|
||||
'key_normal': '#dee2e6',
|
||||
'key_hover': '#0d6efd',
|
||||
'key_special': '#dc3545',
|
||||
'key_function': '#198754'
|
||||
}
|
||||
|
||||
def show_keyboard(self, entry_widget=None):
|
||||
"""Show the virtual keyboard"""
|
||||
if entry_widget:
|
||||
self.target_entry = entry_widget
|
||||
|
||||
if self.keyboard_window and self.keyboard_window.winfo_exists():
|
||||
self.keyboard_window.lift()
|
||||
return
|
||||
|
||||
self.create_keyboard()
|
||||
|
||||
def hide_keyboard(self):
|
||||
"""Hide the virtual keyboard"""
|
||||
if self.keyboard_window and self.keyboard_window.winfo_exists():
|
||||
self.keyboard_window.destroy()
|
||||
self.keyboard_window = None
|
||||
|
||||
def create_keyboard(self):
|
||||
"""Create the virtual keyboard window"""
|
||||
self.keyboard_window = tk.Toplevel(self.parent)
|
||||
self.keyboard_window.title("Virtual Keyboard")
|
||||
self.keyboard_window.configure(bg=self.colors['bg_primary'])
|
||||
self.keyboard_window.resizable(False, False)
|
||||
|
||||
# Make keyboard stay on top
|
||||
self.keyboard_window.attributes('-topmost', True)
|
||||
|
||||
# Position keyboard at bottom of screen
|
||||
self.position_keyboard()
|
||||
|
||||
# Create keyboard layout
|
||||
self.create_keyboard_layout()
|
||||
|
||||
# Bind events
|
||||
self.keyboard_window.protocol("WM_DELETE_WINDOW", self.hide_keyboard)
|
||||
|
||||
def position_keyboard(self):
|
||||
"""Position keyboard at bottom center of screen"""
|
||||
self.keyboard_window.update_idletasks()
|
||||
|
||||
# Get screen dimensions
|
||||
screen_width = self.keyboard_window.winfo_screenwidth()
|
||||
screen_height = self.keyboard_window.winfo_screenheight()
|
||||
|
||||
# Keyboard dimensions
|
||||
kb_width = 800
|
||||
kb_height = 300
|
||||
|
||||
# Position at bottom center
|
||||
x = (screen_width - kb_width) // 2
|
||||
y = screen_height - kb_height - 50 # 50px from bottom
|
||||
|
||||
self.keyboard_window.geometry(f"{kb_width}x{kb_height}+{x}+{y}")
|
||||
|
||||
def create_keyboard_layout(self):
|
||||
"""Create the keyboard layout"""
|
||||
main_frame = tk.Frame(self.keyboard_window, bg=self.colors['bg_primary'], padx=10, pady=10)
|
||||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Title bar
|
||||
title_frame = tk.Frame(main_frame, bg=self.colors['bg_secondary'], height=40)
|
||||
title_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
title_frame.pack_propagate(False)
|
||||
|
||||
title_label = tk.Label(title_frame, text="⌨️ Virtual Keyboard",
|
||||
font=('Segoe UI', 12, 'bold'),
|
||||
bg=self.colors['bg_secondary'], fg=self.colors['text_primary'])
|
||||
title_label.pack(side=tk.LEFT, padx=10, pady=10)
|
||||
|
||||
# Close button
|
||||
close_btn = tk.Button(title_frame, text="✕", command=self.hide_keyboard,
|
||||
bg=self.colors['key_special'], fg=self.colors['text_primary'],
|
||||
font=('Segoe UI', 12, 'bold'), relief=tk.FLAT, width=3)
|
||||
close_btn.pack(side=tk.RIGHT, padx=10, pady=5)
|
||||
|
||||
# Keyboard rows
|
||||
self.create_keyboard_rows(main_frame)
|
||||
|
||||
def create_keyboard_rows(self, parent):
|
||||
"""Create keyboard rows"""
|
||||
# Define keyboard layout
|
||||
rows = [
|
||||
['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace'],
|
||||
['Tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
|
||||
['Caps', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", 'Enter'],
|
||||
['Shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'Shift'],
|
||||
['Ctrl', 'Alt', 'Space', 'Alt', 'Ctrl']
|
||||
]
|
||||
|
||||
# Special keys with different sizes
|
||||
special_keys = {
|
||||
'Backspace': 2,
|
||||
'Tab': 1.5,
|
||||
'Enter': 2,
|
||||
'Caps': 1.8,
|
||||
'Shift': 2.3,
|
||||
'Ctrl': 1.2,
|
||||
'Alt': 1.2,
|
||||
'Space': 6
|
||||
}
|
||||
|
||||
for row_index, row in enumerate(rows):
|
||||
row_frame = tk.Frame(parent, bg=self.colors['bg_primary'])
|
||||
row_frame.pack(fill=tk.X, pady=2)
|
||||
|
||||
for key in row:
|
||||
width = special_keys.get(key, 1)
|
||||
self.create_key_button(row_frame, key, width)
|
||||
|
||||
def create_key_button(self, parent, key, width=1):
|
||||
"""Create a keyboard key button"""
|
||||
# Determine key type and color
|
||||
if key in ['Backspace', 'Tab', 'Enter', 'Caps', 'Shift', 'Ctrl', 'Alt']:
|
||||
bg_color = self.colors['key_function']
|
||||
elif key == 'Space':
|
||||
bg_color = self.colors['key_normal']
|
||||
else:
|
||||
bg_color = self.colors['key_normal']
|
||||
|
||||
# Calculate button width
|
||||
base_width = 4
|
||||
button_width = int(base_width * width)
|
||||
|
||||
# Display text for special keys
|
||||
display_text = {
|
||||
'Backspace': '⌫',
|
||||
'Tab': '⇥',
|
||||
'Enter': '⏎',
|
||||
'Caps': '⇪',
|
||||
'Shift': '⇧',
|
||||
'Ctrl': 'Ctrl',
|
||||
'Alt': 'Alt',
|
||||
'Space': '___'
|
||||
}.get(key, key.upper() if self.caps_lock or self.shift_pressed else key)
|
||||
|
||||
button = tk.Button(parent, text=display_text,
|
||||
command=lambda k=key: self.key_pressed(k),
|
||||
bg=bg_color, fg=self.colors['text_primary'],
|
||||
font=('Segoe UI', 10, 'bold'),
|
||||
relief=tk.FLAT, bd=1,
|
||||
width=button_width, height=2)
|
||||
|
||||
# Add hover effects
|
||||
def on_enter(e, btn=button):
|
||||
btn.configure(bg=self.colors['key_hover'])
|
||||
|
||||
def on_leave(e, btn=button):
|
||||
btn.configure(bg=bg_color)
|
||||
|
||||
button.bind("<Enter>", on_enter)
|
||||
button.bind("<Leave>", on_leave)
|
||||
|
||||
button.pack(side=tk.LEFT, padx=1, pady=1)
|
||||
|
||||
def key_pressed(self, key):
|
||||
"""Handle key press"""
|
||||
if not self.target_entry:
|
||||
return
|
||||
|
||||
if key == 'Backspace':
|
||||
current_pos = self.target_entry.index(tk.INSERT)
|
||||
if current_pos > 0:
|
||||
self.target_entry.delete(current_pos - 1)
|
||||
|
||||
elif key == 'Tab':
|
||||
self.target_entry.insert(tk.INSERT, '\t')
|
||||
|
||||
elif key == 'Enter':
|
||||
# Try to trigger any bound return event
|
||||
self.target_entry.event_generate('<Return>')
|
||||
|
||||
elif key == 'Caps':
|
||||
self.caps_lock = not self.caps_lock
|
||||
self.update_key_display()
|
||||
|
||||
elif key == 'Shift':
|
||||
self.shift_pressed = not self.shift_pressed
|
||||
self.update_key_display()
|
||||
|
||||
elif key == 'Space':
|
||||
self.target_entry.insert(tk.INSERT, ' ')
|
||||
|
||||
elif key in ['Ctrl', 'Alt']:
|
||||
# These could be used for key combinations in the future
|
||||
pass
|
||||
|
||||
else:
|
||||
# Regular character
|
||||
char = key.upper() if self.caps_lock or self.shift_pressed else key
|
||||
|
||||
# Handle shifted characters
|
||||
if self.shift_pressed and not self.caps_lock:
|
||||
shift_map = {
|
||||
'1': '!', '2': '@', '3': '#', '4': '$', '5': '%',
|
||||
'6': '^', '7': '&', '8': '*', '9': '(', '0': ')',
|
||||
'-': '_', '=': '+', '[': '{', ']': '}', '\\': '|',
|
||||
';': ':', "'": '"', ',': '<', '.': '>', '/': '?',
|
||||
'`': '~'
|
||||
}
|
||||
char = shift_map.get(key, char)
|
||||
|
||||
self.target_entry.insert(tk.INSERT, char)
|
||||
|
||||
# Reset shift after character input
|
||||
if self.shift_pressed:
|
||||
self.shift_pressed = False
|
||||
self.update_key_display()
|
||||
|
||||
def update_key_display(self):
|
||||
"""Update key display based on caps lock and shift state"""
|
||||
# This would update the display of keys, but for simplicity
|
||||
# we'll just recreate the keyboard when needed
|
||||
pass
|
||||
|
||||
|
||||
class TouchOptimizedEntry(tk.Entry):
|
||||
"""Entry widget optimized for touch displays with virtual keyboard"""
|
||||
|
||||
def __init__(self, parent, virtual_keyboard=None, **kwargs):
|
||||
# Make entry larger for touch
|
||||
kwargs.setdefault('font', ('Segoe UI', 12))
|
||||
kwargs.setdefault('relief', tk.FLAT)
|
||||
kwargs.setdefault('bd', 8)
|
||||
|
||||
super().__init__(parent, **kwargs)
|
||||
|
||||
self.virtual_keyboard = virtual_keyboard
|
||||
|
||||
# Bind focus events to show/hide keyboard
|
||||
self.bind('<FocusIn>', self.on_focus_in)
|
||||
self.bind('<Button-1>', self.on_click)
|
||||
|
||||
def on_focus_in(self, event):
|
||||
"""Show virtual keyboard when entry gets focus"""
|
||||
if self.virtual_keyboard:
|
||||
self.virtual_keyboard.show_keyboard(self)
|
||||
|
||||
def on_click(self, event):
|
||||
"""Show virtual keyboard when entry is clicked"""
|
||||
if self.virtual_keyboard:
|
||||
self.virtual_keyboard.show_keyboard(self)
|
||||
|
||||
|
||||
class TouchOptimizedButton(tk.Button):
|
||||
"""Button widget optimized for touch displays"""
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
# Make buttons larger for touch
|
||||
kwargs.setdefault('font', ('Segoe UI', 11, 'bold'))
|
||||
kwargs.setdefault('relief', tk.FLAT)
|
||||
kwargs.setdefault('padx', 20)
|
||||
kwargs.setdefault('pady', 12)
|
||||
kwargs.setdefault('cursor', 'hand2')
|
||||
|
||||
super().__init__(parent, **kwargs)
|
||||
|
||||
# Add touch feedback
|
||||
self.bind('<Button-1>', self.on_touch_down)
|
||||
self.bind('<ButtonRelease-1>', self.on_touch_up)
|
||||
|
||||
def on_touch_down(self, event):
|
||||
"""Visual feedback when button is touched"""
|
||||
self.configure(relief=tk.SUNKEN)
|
||||
|
||||
def on_touch_up(self, event):
|
||||
"""Reset visual feedback when touch is released"""
|
||||
self.configure(relief=tk.FLAT)
|
||||
|
||||
|
||||
# Test the virtual keyboard
|
||||
if __name__ == "__main__":
|
||||
def test_virtual_keyboard():
|
||||
root = tk.Tk()
|
||||
root.title("Virtual Keyboard Test")
|
||||
root.geometry("600x400")
|
||||
root.configure(bg='#2f3136')
|
||||
|
||||
# Create virtual keyboard instance
|
||||
vk = VirtualKeyboard(root, dark_theme=True)
|
||||
|
||||
# Test frame
|
||||
test_frame = tk.Frame(root, bg='#2f3136', padx=20, pady=20)
|
||||
test_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Title
|
||||
tk.Label(test_frame, text="🎮 Touch Display Test",
|
||||
font=('Segoe UI', 16, 'bold'),
|
||||
bg='#2f3136', fg='white').pack(pady=20)
|
||||
|
||||
# Test entries
|
||||
tk.Label(test_frame, text="Click entries to show virtual keyboard:",
|
||||
bg='#2f3136', fg='white', font=('Segoe UI', 12)).pack(pady=10)
|
||||
|
||||
entry1 = TouchOptimizedEntry(test_frame, vk, width=30, bg='#36393f',
|
||||
fg='white', insertbackground='white')
|
||||
entry1.pack(pady=10)
|
||||
|
||||
entry2 = TouchOptimizedEntry(test_frame, vk, width=30, bg='#36393f',
|
||||
fg='white', insertbackground='white')
|
||||
entry2.pack(pady=10)
|
||||
|
||||
# Test buttons
|
||||
TouchOptimizedButton(test_frame, text="Show Keyboard",
|
||||
command=lambda: vk.show_keyboard(entry1),
|
||||
bg='#7289da', fg='white').pack(pady=10)
|
||||
|
||||
TouchOptimizedButton(test_frame, text="Hide Keyboard",
|
||||
command=vk.hide_keyboard,
|
||||
bg='#ed4245', fg='white').pack(pady=5)
|
||||
|
||||
root.mainloop()
|
||||
|
||||
test_virtual_keyboard()
|
||||
Reference in New Issue
Block a user