|
@@ -0,0 +1,240 @@
|
|
|
+import yaml
|
|
|
+from os import path, getenv, name
|
|
|
+from kivy.app import App
|
|
|
+from kivy.uix.gridlayout import GridLayout
|
|
|
+from kivy.uix.button import Button
|
|
|
+from kivy.uix.boxlayout import BoxLayout
|
|
|
+from kivy.uix.textinput import TextInput
|
|
|
+from kivy.uix.label import Label
|
|
|
+from kivy.uix.popup import Popup
|
|
|
+from kivy.uix.filechooser import FileChooserIconView
|
|
|
+from kivy.core.window import Window
|
|
|
+from kivy.uix.widget import Widget
|
|
|
+from kivy.properties import ObjectProperty, ListProperty
|
|
|
+
|
|
|
+
|
|
|
+class DraggableButton(Button):
|
|
|
+ """A button that can be dragged."""
|
|
|
+
|
|
|
+ original_pos = ListProperty([0, 0])
|
|
|
+
|
|
|
+ def __init__(self, **kwargs):
|
|
|
+ super().__init__(**kwargs)
|
|
|
+ self.dragged = False
|
|
|
+
|
|
|
+ def on_touch_down(self, touch):
|
|
|
+ if self.collide_point(*touch.pos):
|
|
|
+ self.dragged = True
|
|
|
+ self.original_pos = self.pos
|
|
|
+ return True
|
|
|
+ return super().on_touch_down(touch)
|
|
|
+
|
|
|
+ def on_touch_move(self, touch):
|
|
|
+ if self.dragged:
|
|
|
+ self.center = touch.pos
|
|
|
+ return True
|
|
|
+ return super().on_touch_move(touch)
|
|
|
+
|
|
|
+ def on_touch_up(self, touch):
|
|
|
+ if self.dragged:
|
|
|
+ self.dragged = False
|
|
|
+ self.parent.handle_drop(self, touch)
|
|
|
+ return True
|
|
|
+ return super().on_touch_up(touch)
|
|
|
+
|
|
|
+
|
|
|
+class DraggableGridLayout(GridLayout):
|
|
|
+ """A grid layout that supports drag-and-drop."""
|
|
|
+
|
|
|
+ def __init__(self, app, **kwargs):
|
|
|
+ super().__init__(**kwargs)
|
|
|
+ self.app = app # Reference to the main app
|
|
|
+
|
|
|
+ def handle_drop(self, dragged_button, touch):
|
|
|
+ """Handle the drop of a button."""
|
|
|
+ for child in self.children:
|
|
|
+ if child.collide_point(*touch.pos) and child != dragged_button:
|
|
|
+ # Swap buttons
|
|
|
+ dragged_index = self.children.index(dragged_button)
|
|
|
+ target_index = self.children.index(child)
|
|
|
+ self.children[dragged_index], self.children[target_index] = (
|
|
|
+ self.children[target_index],
|
|
|
+ self.children[dragged_index],
|
|
|
+ )
|
|
|
+
|
|
|
+ # Update the config
|
|
|
+ self.app.swap_button_positions(dragged_index, target_index)
|
|
|
+ break
|
|
|
+
|
|
|
+ # Reset position of dragged button
|
|
|
+ dragged_button.pos = dragged_button.original_pos
|
|
|
+
|
|
|
+
|
|
|
+class ConfigEditorApp(App):
|
|
|
+ def __init__(self, **kwargs):
|
|
|
+ super().__init__(**kwargs)
|
|
|
+ self.config_data = {}
|
|
|
+ self.current_file = None
|
|
|
+ self.selected_button = None
|
|
|
+
|
|
|
+ def load_config_file(self, file_path):
|
|
|
+ """Load the configuration file."""
|
|
|
+ try:
|
|
|
+ with open(file_path, "r", encoding="utf-8") as f:
|
|
|
+ self.config_data = yaml.safe_load(f)
|
|
|
+ self.current_file = file_path
|
|
|
+ return True
|
|
|
+ except Exception as e:
|
|
|
+ self.show_popup("Error", f"Failed to load config: {e}")
|
|
|
+ return False
|
|
|
+
|
|
|
+ def save_config(self):
|
|
|
+ """Save the updated configuration file."""
|
|
|
+ if self.current_file:
|
|
|
+ try:
|
|
|
+ with open(self.current_file, "w", encoding="utf-8") as f:
|
|
|
+ yaml.dump(self.config_data, f)
|
|
|
+ self.show_popup("Success", "Configuration saved successfully!")
|
|
|
+ except Exception as e:
|
|
|
+ self.show_popup("Error", f"Failed to save config: {e}")
|
|
|
+ else:
|
|
|
+ self.show_popup("Error", "No file loaded to save.")
|
|
|
+
|
|
|
+ def show_popup(self, title, message):
|
|
|
+ """Display a popup message."""
|
|
|
+ popup_layout = BoxLayout(orientation="vertical", padding=10, spacing=10)
|
|
|
+ popup_label = Label(text=message)
|
|
|
+ popup_close = Button(text="Close", size_hint=(1, 0.3))
|
|
|
+ popup_layout.add_widget(popup_label)
|
|
|
+ popup_layout.add_widget(popup_close)
|
|
|
+
|
|
|
+ popup = Popup(title=title, content=popup_layout, size_hint=(0.5, 0.5))
|
|
|
+ popup_close.bind(on_release=popup.dismiss)
|
|
|
+ popup.open()
|
|
|
+
|
|
|
+ def build(self):
|
|
|
+ """Build the main UI."""
|
|
|
+ root_layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
|
|
|
+
|
|
|
+ # Top: File Controls
|
|
|
+ file_controls = BoxLayout(size_hint_y=0.1, spacing=10)
|
|
|
+ load_button = Button(text="Load Config", size_hint_x=0.3)
|
|
|
+ save_button = Button(text="Save Config", size_hint_x=0.3)
|
|
|
+ file_controls.add_widget(load_button)
|
|
|
+ file_controls.add_widget(save_button)
|
|
|
+ root_layout.add_widget(file_controls)
|
|
|
+
|
|
|
+ # Middle: Grid Layout for Buttons
|
|
|
+ self.grid = DraggableGridLayout(cols=4, spacing=5, padding=5, app=self)
|
|
|
+ root_layout.add_widget(self.grid)
|
|
|
+
|
|
|
+ # Bottom: Property Editor
|
|
|
+ self.property_editor = BoxLayout(size_hint_y=0.2, spacing=10, padding=5)
|
|
|
+ self.property_editor.add_widget(Label(text="Text:"))
|
|
|
+ self.text_input = TextInput(hint_text="Button Text")
|
|
|
+ self.property_editor.add_widget(self.text_input)
|
|
|
+
|
|
|
+ self.property_editor.add_widget(Label(text="BG Color:"))
|
|
|
+ self.color_input = TextInput(hint_text="Hex Color (e.g., #FFFFFF)")
|
|
|
+ self.property_editor.add_widget(self.color_input)
|
|
|
+
|
|
|
+ save_changes_button = Button(text="Save Changes", size_hint_x=0.3)
|
|
|
+ self.property_editor.add_widget(save_changes_button)
|
|
|
+ root_layout.add_widget(self.property_editor)
|
|
|
+
|
|
|
+ # Bindings
|
|
|
+ load_button.bind(on_release=self.show_file_chooser)
|
|
|
+ save_button.bind(on_release=lambda _: self.save_config())
|
|
|
+ save_changes_button.bind(on_release=self.update_button_properties)
|
|
|
+
|
|
|
+ return root_layout
|
|
|
+
|
|
|
+ def show_file_chooser(self, instance):
|
|
|
+ """Open a file chooser to load a configuration file."""
|
|
|
+ chooser_layout = BoxLayout(
|
|
|
+ orientation="vertical", spacing=10, padding=10
|
|
|
+ )
|
|
|
+ file_chooser = FileChooserIconView(filters=["*.yml", "*.yaml"])
|
|
|
+ chooser_layout.add_widget(file_chooser)
|
|
|
+
|
|
|
+ chooser_buttons = BoxLayout(size_hint_y=0.2)
|
|
|
+ load_button = Button(text="Load")
|
|
|
+ cancel_button = Button(text="Cancel")
|
|
|
+ chooser_buttons.add_widget(load_button)
|
|
|
+ chooser_buttons.add_widget(cancel_button)
|
|
|
+ chooser_layout.add_widget(chooser_buttons)
|
|
|
+
|
|
|
+ popup = Popup(
|
|
|
+ title="Load Config File",
|
|
|
+ content=chooser_layout,
|
|
|
+ size_hint=(0.8, 0.8),
|
|
|
+ )
|
|
|
+ cancel_button.bind(on_release=popup.dismiss)
|
|
|
+ load_button.bind(
|
|
|
+ on_release=lambda _: self.load_config_and_refresh(
|
|
|
+ file_chooser.selection[0], popup
|
|
|
+ )
|
|
|
+ )
|
|
|
+ popup.open()
|
|
|
+
|
|
|
+ def load_config_and_refresh(self, file_path, popup):
|
|
|
+ """Load the configuration and refresh the UI."""
|
|
|
+ if self.load_config_file(file_path):
|
|
|
+ self.refresh_buttons()
|
|
|
+ popup.dismiss()
|
|
|
+
|
|
|
+ def refresh_buttons(self):
|
|
|
+ """Refresh the grid with buttons from the configuration."""
|
|
|
+ self.grid.clear_widgets()
|
|
|
+ if not self.config_data.get("buttons"):
|
|
|
+ return
|
|
|
+
|
|
|
+ for button_data in self.config_data["buttons"]:
|
|
|
+ btn = DraggableButton(
|
|
|
+ text=button_data.get("txt", ""),
|
|
|
+ background_color=self.hex_to_rgba(
|
|
|
+ button_data.get("bg_color", "#cccccc")
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ btn.bind(
|
|
|
+ on_release=lambda instance, data=button_data: self.select_button(
|
|
|
+ instance, data
|
|
|
+ )
|
|
|
+ )
|
|
|
+ self.grid.add_widget(btn)
|
|
|
+
|
|
|
+ def select_button(self, button, data):
|
|
|
+ """Select a button to edit its properties."""
|
|
|
+ self.selected_button = data
|
|
|
+ self.text_input.text = data.get("txt", "")
|
|
|
+ self.color_input.text = data.get("bg_color", "#cccccc")
|
|
|
+
|
|
|
+ def update_button_properties(self, instance):
|
|
|
+ """Update the selected button's properties."""
|
|
|
+ if not self.selected_button:
|
|
|
+ self.show_popup("Error", "No button selected!")
|
|
|
+ return
|
|
|
+
|
|
|
+ self.selected_button["txt"] = self.text_input.text
|
|
|
+ self.selected_button["bg_color"] = self.color_input.text
|
|
|
+ self.refresh_buttons()
|
|
|
+
|
|
|
+ def swap_button_positions(self, index1, index2):
|
|
|
+ """Swap button positions in the configuration."""
|
|
|
+ (
|
|
|
+ self.config_data["buttons"][index1],
|
|
|
+ self.config_data["buttons"][index2],
|
|
|
+ ) = (
|
|
|
+ self.config_data["buttons"][index2],
|
|
|
+ self.config_data["buttons"][index1],
|
|
|
+ )
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def hex_to_rgba(hex_color):
|
|
|
+ """Convert hex color to RGBA."""
|
|
|
+ hex_color = hex_color.lstrip("#")
|
|
|
+ return [int(hex_color[i : i + 2], 16) / 255 for i in (0, 2, 4)] + [1]
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ ConfigEditorApp().run()
|