134 lines
4.1 KiB
Python
134 lines
4.1 KiB
Python
import subprocess
|
|
from typing import Generator, List
|
|
|
|
import ijson
|
|
from fabric.utils import remove_handler, get_relative_path
|
|
from fabric.widgets.box import Box
|
|
from fabric.widgets.button import Button
|
|
from fabric.widgets.entry import Entry
|
|
from fabric.widgets.label import Label
|
|
from fabric.widgets.scrolledwindow import ScrolledWindow
|
|
|
|
|
|
class Emoji(Box):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(
|
|
name="emoji-launcher",
|
|
all_visible=False,
|
|
visible=False,
|
|
**kwargs,
|
|
)
|
|
|
|
self.launcher = kwargs["launcher"]
|
|
self._arranger_handler: int = 0
|
|
self.emoji_manager = EmojiManager(self)
|
|
self.viewport = None
|
|
|
|
self.search_entry = Entry(
|
|
name="search-entry",
|
|
h_expand=True,
|
|
notify_text=lambda entry, *_: self.handle_search_input(entry.get_text()),
|
|
on_activate=lambda entry, *_: self.handle_search_input(entry.get_text()),
|
|
)
|
|
|
|
self.header_box = Box(
|
|
name="header-box",
|
|
spacing=10,
|
|
orientation="h",
|
|
children=[
|
|
self.search_entry,
|
|
],
|
|
)
|
|
|
|
self.launcher_box = Box(
|
|
spacing=10,
|
|
orientation="v",
|
|
h_expand=True,
|
|
children=[self.header_box],
|
|
)
|
|
|
|
self.add(self.launcher_box)
|
|
|
|
def open_launcher(self):
|
|
if not self.viewport:
|
|
self.viewport = Box(name="viewport", spacing=4, orientation="v")
|
|
self.scrolled_window = ScrolledWindow(
|
|
name="scrolled-window",
|
|
spacing=10,
|
|
h_scrollbar_policy="never",
|
|
v_scrollbar_policy="never",
|
|
child=self.viewport,
|
|
)
|
|
self.launcher_box.add(self.scrolled_window)
|
|
|
|
self.viewport.children = []
|
|
self.emoji_manager.arrange_viewport()
|
|
|
|
self.viewport.show()
|
|
self.search_entry.grab_focus()
|
|
|
|
def close_launcher(self):
|
|
self.launcher.close()
|
|
|
|
def handle_search_input(self, text: str):
|
|
self.emoji_manager.arrange_viewport(text)
|
|
|
|
|
|
class EmojiManager:
|
|
def __init__(self, launcher):
|
|
self.launcher = launcher
|
|
self.emoji_file_path = get_relative_path("../../../config/assets/emoji.json")
|
|
|
|
def load_emojis(self) -> Generator[tuple, None, None]:
|
|
try:
|
|
with open(self.emoji_file_path, "r") as file:
|
|
for emoji_str, item in ijson.kvitems(file, ""):
|
|
yield emoji_str, item["name"], item["slug"], item["group"]
|
|
except (ijson.JSONError, KeyError, OSError) as e:
|
|
print(f"Error loading emojis: {e}")
|
|
return
|
|
|
|
def query_emojis(self, query: str) -> List[tuple]:
|
|
query = query.lower()
|
|
return [
|
|
(emoji_str, name, slug, group)
|
|
for emoji_str, name, slug, group in self.load_emojis()
|
|
if query in name.lower() or query in slug.lower() or query in group.lower()
|
|
][:48]
|
|
|
|
def copy_emoji(self, emoji: tuple):
|
|
subprocess.run(["wl-copy"], input=emoji[0].encode(), check=True)
|
|
self.launcher.close_launcher()
|
|
|
|
def bake_emoji_slot(self, emoji: tuple, **kwargs) -> Button:
|
|
return Button(
|
|
name="emoji-item",
|
|
child=Label(label=emoji[0], h_align="center"),
|
|
tooltip_text=emoji[1],
|
|
on_clicked=lambda *_: self.copy_emoji(emoji),
|
|
**kwargs,
|
|
)
|
|
|
|
def arrange_viewport(self, query: str = ""):
|
|
if not self.launcher.viewport:
|
|
return
|
|
|
|
(
|
|
remove_handler(self.launcher._arranger_handler)
|
|
if self.launcher._arranger_handler
|
|
else None
|
|
)
|
|
self.launcher.viewport.children = []
|
|
|
|
filtered_emojis = self.query_emojis(query)
|
|
|
|
row = Box(name="emoji-row", spacing=10, orientation="h")
|
|
for emoji in filtered_emojis:
|
|
row.add(self.bake_emoji_slot(emoji))
|
|
if len(row.children) >= 12:
|
|
self.launcher.viewport.add(row)
|
|
row = Box(name="emoji-row", spacing=10, orientation="h")
|
|
|
|
if row.children:
|
|
self.launcher.viewport.add(row)
|