Settings.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env python3
  2. # Copyright © 2024 Noah Vogt <noah@noahvogt.com>
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. # You should have received a copy of the GNU General Public License
  12. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. # pylint: disable=invalid-name
  14. import subprocess
  15. import sys
  16. import colorama
  17. from kivy.app import App
  18. from kivy.uix.button import Button
  19. from kivy.uix.gridlayout import GridLayout
  20. from kivy.utils import get_color_from_hex
  21. from kivy.properties import ListProperty # pylint: disable=no-name-in-module
  22. from util import log, error_exit_gui
  23. from config import get_config_path, ConfigLoader, Config
  24. class DraggableButton(Button):
  25. """A button that can be dragged and adjusts font size dynamically."""
  26. original_pos = ListProperty([0, 0])
  27. def __init__(self, **kwargs):
  28. super().__init__(**kwargs)
  29. self.dragged = False
  30. self.bind(size=self.adjust_font_size, text=self.adjust_font_size)
  31. def adjust_font_size(self, *args):
  32. """Dynamically adjusts font size to fit the button's bounds."""
  33. if not self.text:
  34. return
  35. # Define the minimum and maximum font size
  36. min_font_size = 10
  37. max_font_size = min(self.width, self.height) * 0.5
  38. # Perform a binary search to find the best font size
  39. best_font_size = min_font_size
  40. while min_font_size <= max_font_size:
  41. current_font_size = (min_font_size + max_font_size) // 2
  42. self.font_size = current_font_size
  43. self.texture_update() # Update texture to get new texture_size
  44. # Check if the text fits within the button's bounds
  45. if (
  46. self.texture_size[0] <= self.width * 0.9
  47. and self.texture_size[1] <= self.height * 0.9
  48. ):
  49. best_font_size = current_font_size
  50. min_font_size = current_font_size + 1
  51. else:
  52. max_font_size = current_font_size - 1
  53. # Apply the best font size
  54. self.font_size = best_font_size
  55. def on_touch_down(self, touch):
  56. if self.collide_point(*touch.pos):
  57. self.dragged = True
  58. self.original_pos = self.pos
  59. self.parent.canvas.remove(self.canvas)
  60. self.parent.canvas.add(self.canvas)
  61. return True
  62. return super().on_touch_down(touch)
  63. def on_touch_move(self, touch):
  64. if self.dragged:
  65. self.center = touch.pos
  66. return True
  67. return super().on_touch_move(touch)
  68. def on_touch_up(self, touch):
  69. if self.dragged:
  70. self.dragged = False
  71. self.parent.handle_drop(self, touch)
  72. return True
  73. return super().on_touch_up(touch)
  74. class DraggableGridLayout(GridLayout):
  75. """A grid layout that supports drag-and-drop."""
  76. def __init__(self, app, **kwargs):
  77. super().__init__(**kwargs)
  78. self.app = app # Reference to the main app
  79. def handle_drop(self, dragged_button, touch):
  80. """Handle the drop of a button."""
  81. for child in self.children:
  82. if child.collide_point(*touch.pos) and child != dragged_button:
  83. # Swap buttons
  84. dragged_index = self.children.index(dragged_button)
  85. target_index = self.children.index(child)
  86. self.children[dragged_index], self.children[target_index] = (
  87. self.children[target_index],
  88. self.children[dragged_index],
  89. )
  90. # Update the config
  91. print(f"dragged: {dragged_index}")
  92. print(f"target: {target_index}")
  93. self.app.swap_button_positions(dragged_index, target_index)
  94. break
  95. # Reset position of dragged button
  96. dragged_button.pos = dragged_button.original_pos
  97. class VulcanBoardApp(App):
  98. def build(self):
  99. config_loader = ConfigLoader(get_config_path())
  100. config = config_loader.get_config() # pyright: ignore
  101. if isinstance(config, str):
  102. error_exit_gui(config)
  103. else:
  104. config: Config = config
  105. button_map = {
  106. (btn["position"][0], btn["position"][1]): btn
  107. for btn in config.buttons
  108. }
  109. layout = DraggableGridLayout(
  110. self,
  111. cols=config.columns,
  112. rows=config.rows,
  113. spacing=config.spacing,
  114. padding=config.padding,
  115. )
  116. # Populate grid with buttons and placeholders
  117. for row in range(config.rows):
  118. for col in range(config.columns):
  119. defined_button = button_map.get((row, col))
  120. if defined_button:
  121. btn = DraggableButton(
  122. text=defined_button.get("txt", ""),
  123. background_color=get_color_from_hex(
  124. defined_button.get("bg_color", "aaaaff")
  125. ),
  126. color=get_color_from_hex(
  127. defined_button.get("fg_color", "ffffff")
  128. ),
  129. font_size=defined_button.get("fontsize", 14),
  130. halign="center",
  131. valign="middle",
  132. background_normal="",
  133. )
  134. cmd = defined_button.get("cmd", "")
  135. # pylint: disable=no-member
  136. btn.bind( # pyright: ignore
  137. on_release=lambda _, cmd=cmd: self.execute_command_async(
  138. cmd
  139. )
  140. )
  141. else:
  142. btn = DraggableButton(
  143. background_color=get_color_from_hex("cccccc"),
  144. )
  145. layout.add_widget(btn)
  146. return layout
  147. def swap_button_positions(self, index1, index2):
  148. pass
  149. def config_error_exit(self, popup):
  150. popup.dismiss()
  151. sys.exit(1)
  152. def execute_command_async(self, cmd):
  153. if cmd:
  154. try:
  155. subprocess.Popen( # pylint: disable=consider-using-with
  156. cmd, shell=True
  157. )
  158. log(f"Executed command: {cmd}")
  159. except Exception as e:
  160. log(f"Error executing command: {e}", color="yellow")
  161. if __name__ == "__main__":
  162. colorama.init()
  163. VulcanBoardApp().run()