ConfigEditorApp.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import yaml
  2. from os import path, getenv, name
  3. from kivy.app import App
  4. from kivy.uix.gridlayout import GridLayout
  5. from kivy.uix.button import Button
  6. from kivy.uix.boxlayout import BoxLayout
  7. from kivy.uix.textinput import TextInput
  8. from kivy.uix.label import Label
  9. from kivy.uix.popup import Popup
  10. from kivy.uix.filechooser import FileChooserIconView
  11. from kivy.core.window import Window
  12. from kivy.uix.widget import Widget
  13. from kivy.properties import ObjectProperty, ListProperty
  14. class DraggableButton(Button):
  15. """A button that can be dragged."""
  16. original_pos = ListProperty([0, 0])
  17. def __init__(self, **kwargs):
  18. super().__init__(**kwargs)
  19. self.dragged = False
  20. def on_touch_down(self, touch):
  21. if self.collide_point(*touch.pos):
  22. self.dragged = True
  23. self.original_pos = self.pos
  24. return True
  25. return super().on_touch_down(touch)
  26. def on_touch_move(self, touch):
  27. if self.dragged:
  28. self.center = touch.pos
  29. return True
  30. return super().on_touch_move(touch)
  31. def on_touch_up(self, touch):
  32. if self.dragged:
  33. self.dragged = False
  34. self.parent.handle_drop(self, touch)
  35. return True
  36. return super().on_touch_up(touch)
  37. class DraggableGridLayout(GridLayout):
  38. """A grid layout that supports drag-and-drop."""
  39. def __init__(self, app, **kwargs):
  40. super().__init__(**kwargs)
  41. self.app = app # Reference to the main app
  42. def handle_drop(self, dragged_button, touch):
  43. """Handle the drop of a button."""
  44. for child in self.children:
  45. if child.collide_point(*touch.pos) and child != dragged_button:
  46. # Swap buttons
  47. dragged_index = self.children.index(dragged_button)
  48. target_index = self.children.index(child)
  49. self.children[dragged_index], self.children[target_index] = (
  50. self.children[target_index],
  51. self.children[dragged_index],
  52. )
  53. # Update the config
  54. self.app.swap_button_positions(dragged_index, target_index)
  55. break
  56. # Reset position of dragged button
  57. dragged_button.pos = dragged_button.original_pos
  58. class ConfigEditorApp(App):
  59. def __init__(self, **kwargs):
  60. super().__init__(**kwargs)
  61. self.config_data = {}
  62. self.current_file = None
  63. self.selected_button = None
  64. def load_config_file(self, file_path):
  65. """Load the configuration file."""
  66. try:
  67. with open(file_path, "r", encoding="utf-8") as f:
  68. self.config_data = yaml.safe_load(f)
  69. self.current_file = file_path
  70. return True
  71. except Exception as e:
  72. self.show_popup("Error", f"Failed to load config: {e}")
  73. return False
  74. def save_config(self):
  75. """Save the updated configuration file."""
  76. if self.current_file:
  77. try:
  78. with open(self.current_file, "w", encoding="utf-8") as f:
  79. yaml.dump(self.config_data, f)
  80. self.show_popup("Success", "Configuration saved successfully!")
  81. except Exception as e:
  82. self.show_popup("Error", f"Failed to save config: {e}")
  83. else:
  84. self.show_popup("Error", "No file loaded to save.")
  85. def show_popup(self, title, message):
  86. """Display a popup message."""
  87. popup_layout = BoxLayout(orientation="vertical", padding=10, spacing=10)
  88. popup_label = Label(text=message)
  89. popup_close = Button(text="Close", size_hint=(1, 0.3))
  90. popup_layout.add_widget(popup_label)
  91. popup_layout.add_widget(popup_close)
  92. popup = Popup(title=title, content=popup_layout, size_hint=(0.5, 0.5))
  93. popup_close.bind(on_release=popup.dismiss)
  94. popup.open()
  95. def build(self):
  96. """Build the main UI."""
  97. root_layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
  98. # Top: File Controls
  99. file_controls = BoxLayout(size_hint_y=0.1, spacing=10)
  100. load_button = Button(text="Load Config", size_hint_x=0.3)
  101. save_button = Button(text="Save Config", size_hint_x=0.3)
  102. file_controls.add_widget(load_button)
  103. file_controls.add_widget(save_button)
  104. root_layout.add_widget(file_controls)
  105. # Middle: Grid Layout for Buttons
  106. self.grid = DraggableGridLayout(cols=4, spacing=5, padding=5, app=self)
  107. root_layout.add_widget(self.grid)
  108. # Bottom: Property Editor
  109. self.property_editor = BoxLayout(size_hint_y=0.2, spacing=10, padding=5)
  110. self.property_editor.add_widget(Label(text="Text:"))
  111. self.text_input = TextInput(hint_text="Button Text")
  112. self.property_editor.add_widget(self.text_input)
  113. self.property_editor.add_widget(Label(text="BG Color:"))
  114. self.color_input = TextInput(hint_text="Hex Color (e.g., #FFFFFF)")
  115. self.property_editor.add_widget(self.color_input)
  116. save_changes_button = Button(text="Save Changes", size_hint_x=0.3)
  117. self.property_editor.add_widget(save_changes_button)
  118. root_layout.add_widget(self.property_editor)
  119. # Bindings
  120. load_button.bind(on_release=self.show_file_chooser)
  121. save_button.bind(on_release=lambda _: self.save_config())
  122. save_changes_button.bind(on_release=self.update_button_properties)
  123. return root_layout
  124. def show_file_chooser(self, instance):
  125. """Open a file chooser to load a configuration file."""
  126. chooser_layout = BoxLayout(
  127. orientation="vertical", spacing=10, padding=10
  128. )
  129. file_chooser = FileChooserIconView(filters=["*.yml", "*.yaml"])
  130. chooser_layout.add_widget(file_chooser)
  131. chooser_buttons = BoxLayout(size_hint_y=0.2)
  132. load_button = Button(text="Load")
  133. cancel_button = Button(text="Cancel")
  134. chooser_buttons.add_widget(load_button)
  135. chooser_buttons.add_widget(cancel_button)
  136. chooser_layout.add_widget(chooser_buttons)
  137. popup = Popup(
  138. title="Load Config File",
  139. content=chooser_layout,
  140. size_hint=(0.8, 0.8),
  141. )
  142. cancel_button.bind(on_release=popup.dismiss)
  143. load_button.bind(
  144. on_release=lambda _: self.load_config_and_refresh(
  145. file_chooser.selection[0], popup
  146. )
  147. )
  148. popup.open()
  149. def load_config_and_refresh(self, file_path, popup):
  150. """Load the configuration and refresh the UI."""
  151. if self.load_config_file(file_path):
  152. self.refresh_buttons()
  153. popup.dismiss()
  154. def refresh_buttons(self):
  155. """Refresh the grid with buttons from the configuration."""
  156. self.grid.clear_widgets()
  157. if not self.config_data.get("buttons"):
  158. return
  159. for button_data in self.config_data["buttons"]:
  160. btn = DraggableButton(
  161. text=button_data.get("txt", ""),
  162. background_color=self.hex_to_rgba(
  163. button_data.get("bg_color", "#cccccc")
  164. ),
  165. )
  166. btn.bind(
  167. on_release=lambda instance, data=button_data: self.select_button(
  168. instance, data
  169. )
  170. )
  171. self.grid.add_widget(btn)
  172. def select_button(self, button, data):
  173. """Select a button to edit its properties."""
  174. self.selected_button = data
  175. self.text_input.text = data.get("txt", "")
  176. self.color_input.text = data.get("bg_color", "#cccccc")
  177. def update_button_properties(self, instance):
  178. """Update the selected button's properties."""
  179. if not self.selected_button:
  180. self.show_popup("Error", "No button selected!")
  181. return
  182. self.selected_button["txt"] = self.text_input.text
  183. self.selected_button["bg_color"] = self.color_input.text
  184. self.refresh_buttons()
  185. def swap_button_positions(self, index1, index2):
  186. """Swap button positions in the configuration."""
  187. (
  188. self.config_data["buttons"][index1],
  189. self.config_data["buttons"][index2],
  190. ) = (
  191. self.config_data["buttons"][index2],
  192. self.config_data["buttons"][index1],
  193. )
  194. @staticmethod
  195. def hex_to_rgba(hex_color):
  196. """Convert hex color to RGBA."""
  197. hex_color = hex_color.lstrip("#")
  198. return [int(hex_color[i : i + 2], 16) / 255 for i in (0, 2, 4)] + [1]
  199. if __name__ == "__main__":
  200. ConfigEditorApp().run()