123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- 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()
|