diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.idea/crosshair-overlay.iml b/.idea/crosshair-overlay.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/crosshair-overlay.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..f07e606 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,38 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..038558a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d73a4a8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/overlay.iml b/.idea/overlay.iml new file mode 100644 index 0000000..6690b72 --- /dev/null +++ b/.idea/overlay.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..a7b7c03 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1658232308929 + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 89cbcfe..1e748f0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ -# crosshair-overlay -Crosshair overlay +# 💠 Crosshair Overlay + +![GitHub](https://img.shields.io/github/license/I-Atlas/crosshair-overlay?style=flat-square) +![Python](https://img.shields.io/badge/Made%20With-Python%203.10-blue.svg?style=flat-square) + +![crosshair](/screenshots/crosshair.PNG) + +Crosshair overlay is transparent overlay on screen with crosshair or dot. +You can adjust the crosshair using the easy-to-use interface. +There is support for light and dark themes! + + +## 🚀 Start + +1. Download overlay.exe from [releases](https://github.com/I-Atlas/crosshair-overlay/releases); +2. Open overlay.exe; +3. Click Start button; +4. Make sure you play the game in a borderless window mode; +5. Enjoy! + +## 🔮 Setting + +![menu](/screenshots/menu.PNG) + +1. Open or save crosshair settings menu; +2. Change color of crosshair; +3. Change the type of crosshair; +4. Adjustment for crosshair; +5. Reset settings; +6. Start or stop overlay crosshair on screen. + +## 📑 License + +The project is licensed under the [GNU General Public License v3.0](https://github.com/I-Atlas/crosshair-overlay/blob/master/LICENSE). \ No newline at end of file diff --git a/fonts/Inter-Regular.otf b/fonts/Inter-Regular.otf new file mode 100644 index 0000000..fdb121d Binary files /dev/null and b/fonts/Inter-Regular.otf differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2ce8a45 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +altgraph==0.17.2 +dearpygui==1.6.2 +future==0.18.2 +pefile==2022.5.30 +pyinstaller==5.2 +pyinstaller-hooks-contrib==2022.8 +pywin32-ctypes==0.2.0 +darkdetect~=0.7.1 \ No newline at end of file diff --git a/screenshots/crosshair.PNG b/screenshots/crosshair.PNG new file mode 100644 index 0000000..41aa2e4 Binary files /dev/null and b/screenshots/crosshair.PNG differ diff --git a/screenshots/menu.PNG b/screenshots/menu.PNG new file mode 100644 index 0000000..1d05db0 Binary files /dev/null and b/screenshots/menu.PNG differ diff --git a/src/overlay.py b/src/overlay.py new file mode 100644 index 0000000..97636f9 --- /dev/null +++ b/src/overlay.py @@ -0,0 +1,90 @@ +import tkinter as tk + + +class CrosshairOverlay(tk.Tk): + def __init__(self, size=4, width=1, distance=2, color_r=255, color_g=255, color_b=255, is_outline=False, + is_center_dot=False, is_crosshair=True, offset_x=0, offset_y=0): + super().__init__() + self.l = size + self.w = width + self.tn = distance + self.nk = self._nk(self.w, self.tn) + self.L = self._L(self.l, self.nk) + self.LL = self._LL(self.L, self.w) + self.ox = self.winfo_screenwidth() / 2 - self.LL + offset_x + self.oy = self.winfo_screenheight() / 2 - self.LL + offset_y + self.c = "#" + "".join(map(lambda x: format(int(x), "02x"), [color_r, color_g, color_b])).upper() + self.iol = is_outline + self.icd = is_center_dot + self.ich = is_crosshair + + self.geometry(self._gm(self.L + 4, self.L + 4, self.ox, self.oy)) + self.canvas = tk.Canvas(self, bg="snow", width=self.L + self.w, height=self.L + self.w, + highlightthickness=0) + self.canvas.pack(fill=tk.BOTH, expand=True) + + self._init_transparent() + self._create_crosshair_or_dot() + + def _init_transparent(self): + self.overrideredirect(True) + self.wm_attributes("-disabled", True) + self.wm_attributes("-topmost", True) + self.wm_attributes("-transparentcolor", "snow") + + def _create_crosshair_or_dot(self): + if self.ich: + self._create_crosshair() + if self.icd: + self._create_center_dot() + + def _create_crosshair(self): + self._create_crosshair_up() + self._create_crosshair_down() + self._create_crosshair_left() + self._create_crosshair_right() + + def _create_crosshair_up(self): + if self.iol: + self.canvas.create_rectangle(self.LL, 0, self.LL + self.w + 1, self.l + 1, fill=self.c) + else: + self.canvas.create_rectangle(self.LL, 0, self.LL + self.w + 1, self.l + 1, fill=self.c, outline="snow") + + def _create_crosshair_down(self): + if self.iol: + self.canvas.create_rectangle(self.LL, self.l + self.nk, self.LL + self.w + 1, self.L + 1, fill=self.c) + else: + self.canvas.create_rectangle(self.LL, self.l + self.nk, self.LL + self.w + 1, self.L + 1, fill=self.c, + outline="snow") + + def _create_crosshair_left(self): + if self.iol: + self.canvas.create_rectangle(0, self.LL, self.l + 1, self.LL + self.w + 1, fill=self.c) + else: + self.canvas.create_rectangle(0, self.LL, self.l + 1, self.LL + self.w + 1, fill=self.c, outline="snow") + + def _create_crosshair_right(self): + if self.iol: + self.canvas.create_rectangle(self.l + self.nk, self.LL, self.L + 1, self.LL + self.w + 1, fill=self.c) + else: + self.canvas.create_rectangle(self.l + self.nk, self.LL, self.L + 1, self.LL + self.w + 1, fill=self.c, + outline="snow") + + def _create_center_dot(self): + if self.iol: + self.canvas.create_rectangle(self.LL, self.LL, self.LL + self.w + 1, self.LL + self.w + 1, fill=self.c) + else: + self.canvas.create_rectangle(self.LL, self.LL, self.LL + self.w + 1, self.LL + self.w + 1, fill=self.c, + outline="snow") + + def _gm(self, ww, wh, offset_x, offset_y): + return str(int(ww)) + "x" + str(int(wh)) + "+" + str(int(offset_x)) + "+" + str(int(offset_y)) + + def _nk(self, w, tn): + return w + tn * 2 + + def _L(self, l, nk): + return nk + l * 2 + + def _LL(self, L, w): + return (L - w) / 2 \ No newline at end of file diff --git a/src/ui.py b/src/ui.py new file mode 100644 index 0000000..ff55ba7 --- /dev/null +++ b/src/ui.py @@ -0,0 +1,153 @@ +import json +import os.path +import tkinter as tk +import multiprocessing +import dearpygui.dearpygui as dpg +from dearpygui_ext.themes import create_theme_imgui_light, create_theme_imgui_dark +import overlay +import darkdetect + +p = multiprocessing.Process() + + +def _get_theme(): + light_theme = create_theme_imgui_light() + dark_theme = create_theme_imgui_dark() + if darkdetect.isDark(): + return dark_theme + else: + return light_theme + + +def ui_loop(ww=1920, wh=1080): + dpg.create_context() + + with dpg.window(tag="Primary Window"): + with dpg.menu_bar(): + with dpg.menu(label="Menu"): + with dpg.file_dialog(label="File Open", width=400, height=300, show=False, + callback=_open_settings, tag="__file_open"): + dpg.add_file_extension(".json", color=(255, 255, 255, 255)) + dpg.add_menu_item(label="Open", user_data=dpg.last_container(), + callback=lambda s, a, u: dpg.configure_item(u, show=True)) + + with dpg.file_dialog(label="File Save", width=400, height=300, show=False, + callback=_save_settings, tag="__file_save"): + dpg.add_file_extension(".json", color=(255, 255, 255, 255)) + dpg.add_menu_item(label="Save", user_data=dpg.last_container(), + callback=lambda s, a, u: dpg.configure_item(u, show=True)) + + dpg.add_color_edit((255, 255, 255, 255), label="Color", no_alpha=True, callback=_set_values, + tag="color") + + dpg.add_checkbox(label="Crosshair", default_value=True, callback=_set_values, tag="is_crosshair") + dpg.add_checkbox(label="Center Dot", callback=_set_values, tag="is_center_dot") + dpg.add_checkbox(label="Outline", callback=_set_values, tag="is_outline") + + dpg.add_input_int(label="Size", default_value=4, callback=_set_values, tag="size") + dpg.add_input_int(label="Width", default_value=1, callback=_set_values, tag="width") + dpg.add_input_int(label="Center distance", default_value=2, callback=_set_values, tag="distance") + dpg.add_input_int(label="Offset X", callback=_set_values, min_value=int(-ww / 2), + max_value=int(ww / 2), + min_clamped=True, max_clamped=True, tag="offset_x") + dpg.add_input_int(label="Offset Y", callback=_set_values, min_value=int(-wh / 2), + max_value=int(wh / 2), + min_clamped=True, max_clamped=True, + tag="offset_y") + + with dpg.group(label="Buttons", tag="start_stop_buttons", horizontal=True): + dpg.add_button(label="Reset settings", callback=_reset) + dpg.add_button(label="Open overlay", callback=_start) + dpg.add_button(label="Close overlay", callback=_stop) + + theme = _get_theme() + dpg.bind_theme(theme) + + dpg.create_viewport(title='Crosshair Overlay', width=1280, height=720) + dpg.setup_dearpygui() + dpg.show_viewport() + dpg.set_primary_window("Primary Window", True) + dpg.start_dearpygui() + dpg.destroy_context() + + +def _crosshair_overlay(size=4, width=1, distance=2, color_r=255, color_g=255, color_b=255, is_outline=False, + is_center_dot=False, is_crosshair=True, offset_x=0, offset_y=0): + overlay.CrosshairOverlay(size=size, width=width, distance=distance, color_r=color_r, color_g=color_g, + color_b=color_b, + is_outline=is_outline, + is_center_dot=is_center_dot, is_crosshair=is_crosshair, offset_x=offset_x, + offset_y=offset_y).mainloop() + + +def _set_values(): + if p.is_alive(): + _start() + + +def _start(): + global p + if p.is_alive(): + p.kill() + p.join() + p = multiprocessing.Process(target=_crosshair_overlay, kwargs=_get_values(), daemon=True) + p.start() + + +def _stop(): + if p.is_alive(): + p.kill() + p.join() + + +def _get_values(): + r, g, b = list(map(int, dpg.get_value("color")[:3])) + return {"size": dpg.get_value("size"), "width": dpg.get_value("width"), "distance": dpg.get_value("distance"), + "color_r": r, + "color_g": g, "color_b": b, "is_outline": dpg.get_value("is_outline"), + "is_center_dot": dpg.get_value("is_center_dot"), "is_crosshair": dpg.get_value("is_crosshair"), + "offset_x": dpg.get_value("offset_x"), + "offset_y": -dpg.get_value("offset_y")} + + +def _reset(): + dpg.set_value("color", (255, 255, 255, 255)) + dpg.set_value("is_outline", False) + dpg.set_value("is_center_dot", False) + dpg.set_value("is_crosshair", True) + dpg.set_value("size", 4) + dpg.set_value("width", 1) + dpg.set_value("distance", 2) + dpg.set_value("offset_x", 0) + dpg.set_value("offset_y", 0) + _set_values() + + +def _open_settings(sender, app_data): + if os.path.isfile(app_data["file_path_name"]): + with open(app_data["file_path_name"], "r") as fp: + data = json.load(fp) + dpg.set_value("color", (data["color_r"], data["color_g"], data["color_b"], 255)) + dpg.set_value("is_outline", data["is_outline"]) + dpg.set_value("is_center_dot", data["is_center_dot"]) + dpg.set_value("is_crosshair", data["is_crosshair"]) + dpg.set_value("size", data["size"]) + dpg.set_value("width", data["width"]) + dpg.set_value("distance", data["distance"]) + dpg.set_value("offset_x", data["offset_x"]) + dpg.set_value("offset_y", data["offset_y"]) + _set_values() + + +def _save_settings(sender, app_data): + with open(app_data["file_path_name"], "w") as fp: + json.dump(_get_values(), fp) + + +if __name__ == '__main__': + multiprocessing.freeze_support() + root = tk.Tk() + ww = root.winfo_screenwidth() + wh = root.winfo_screenheight() + root.destroy() + ui_loop(ww=ww, wh=wh)