From 7a80477890a84853fdbb61a49228986c2156ba13 Mon Sep 17 00:00:00 2001 From: DasMoorhuhn Date: Wed, 11 Dec 2024 18:31:41 +0100 Subject: [PATCH] first push --- .gitignore | 3 + experiments/window_gtk.py | 57 ++++++++ experiments/window_opengl.py | 90 ++++++++++++ main.py | 0 os_linux.py | 0 os_windows.py | 0 .../demonite/demonite_float_correct.png | Bin 0 -> 712 bytes pets_sprites/demonite/demonite_idle1.png | Bin 0 -> 606 bytes pets_sprites/demonite/demonite_idle2.png | Bin 0 -> 581 bytes pets_sprites/demonite/demonite_idle3.png | Bin 0 -> 604 bytes .../demonite/demonite_moving_right1.png | Bin 0 -> 604 bytes .../demonite/demonite_moving_right2.png | Bin 0 -> 578 bytes pets_sprites/demonite/heart_heart.png | Bin 0 -> 1490 bytes vpet.py | 121 ++++++++++++++++ window_qt.py | 135 ++++++++++++++++++ 15 files changed, 406 insertions(+) create mode 100644 .gitignore create mode 100644 experiments/window_gtk.py create mode 100644 experiments/window_opengl.py create mode 100644 main.py create mode 100644 os_linux.py create mode 100644 os_windows.py create mode 100644 pets_sprites/demonite/demonite_float_correct.png create mode 100644 pets_sprites/demonite/demonite_idle1.png create mode 100644 pets_sprites/demonite/demonite_idle2.png create mode 100644 pets_sprites/demonite/demonite_idle3.png create mode 100644 pets_sprites/demonite/demonite_moving_right1.png create mode 100644 pets_sprites/demonite/demonite_moving_right2.png create mode 100644 pets_sprites/demonite/heart_heart.png create mode 100644 vpet.py create mode 100755 window_qt.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e84bfb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +.idea/ +*.swap diff --git a/experiments/window_gtk.py b/experiments/window_gtk.py new file mode 100644 index 0000000..6e81af0 --- /dev/null +++ b/experiments/window_gtk.py @@ -0,0 +1,57 @@ +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk, GdkPixbuf, cairo + + +class VPetWindow(Gtk.Window): + def __init__(self): + super().__init__() + + # Fensterkonfiguration + self.set_title("vPet") + self.set_default_size(48, 48) + self.set_app_paintable(True) + self.set_decorated(False) + self.set_keep_above(True) + self.set_accept_focus(False) + + # Entferne den Fensterschatten und wende RGBA-Visual an + screen = self.get_screen() + visual = screen.get_rgba_visual() + if visual: + self.set_visual(visual) + + # Mausereignisse ignorieren + self.connect("screen-changed", self.on_screen_changed) + self.set_app_paintable(True) + self.on_screen_changed(None) + + # Lade und zeige das Sprite (virtuelles Haustier) + self.image = Gtk.Image() + pixbuf = GdkPixbuf.Pixbuf.new_from_file("vpet_sprite.png") # Ersetze mit deinem Sprite + self.image.set_from_pixbuf(pixbuf) + self.add(self.image) + + self.show_all() + + def on_screen_changed(self, screen): + gdk_window = self.get_window() + if gdk_window: + # Eingabeform auf das Sprite beschränken + region = Gdk.cairo_region_create_from_surface(self.get_mask()) + gdk_window.input_shape_combine_region(region) + + def get_mask(self): + # Maske für die Eingabe basierend auf der Sprite-Form erstellen + pixbuf = self.image.get_pixbuf() + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, pixbuf.get_width(), pixbuf.get_height()) + cr = cairo.Context(surface) + Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0) + cr.paint() + return surface + + +if __name__ == "__main__": + win = VPetWindow() + win.connect("destroy", Gtk.main_quit) + Gtk.main() diff --git a/experiments/window_opengl.py b/experiments/window_opengl.py new file mode 100644 index 0000000..39c2b0f --- /dev/null +++ b/experiments/window_opengl.py @@ -0,0 +1,90 @@ +import pygame +from OpenGL.GL import * +from OpenGL.GLU import * +from OpenGL.GLUT import * +import sys +import os + + +# Pet Attribute +class VirtualPet: + def __init__(self): + self.happiness = 50 + self.hunger = 50 + + def feed(self): + self.hunger -= 10 + self.happiness += 5 + + def pet(self): + self.happiness += 10 + + def update(self): + self.hunger += 0.1 + self.happiness -= 0.05 + + +# OpenGL-Setup +def init_opengl(): + glEnable(GL_BLEND) # Aktiviert Transparenz + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glEnable(GL_DEPTH_TEST) + glClearColor(0.2, 0.6, 0.8, 0.0) # Himmelblau mit Transparenz (Alpha = 0.5) + gluPerspective(45, (800 / 600), 0.1, 50.0) + + +# Pet zeichnen (Würfel als Platzhalter) +def draw_pet(): + glPushMatrix() + glColor4f(0, 0, 0, 0.2) + glTranslatef(0.0, 0.0, -5) + glRotatef(pygame.time.get_ticks() * 0.05, 0, 1, 0) # Rotation + glutWireCube(1) # Zeichnet den Würfel + glPopMatrix() + + +# Hauptprogramm +def main(): + # Setze Umgebungsvariablen für transparentes Fenster + os.environ['SDL_VIDEODRIVER'] = 'x11' + os.environ['SDL_VIDEO_WINDOW_POS'] = '0,0' + os.environ['SDL_VIDEO_CENTERED'] = '1' + + pygame.init() + display = pygame.display.set_mode((48, 48), pygame.DOUBLEBUF | pygame.OPENGL | pygame.NOFRAME | pygame.SRCALPHA) + pygame.display.set_caption("Transparent Virtual Pet") + clock = pygame.time.Clock() + display.set + + pet = VirtualPet() + glutInit() # Initialisiert GLUT + init_opengl() + + # Setze Transparenz für das Fenster + # pygame.display.set_mode((800, 600), pygame.OPENGL | pygame.DOUBLEBUF | pygame.NOFRAME | pygame.SRCALPHA) + + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_f: + pet.feed() + elif event.key == pygame.K_p: + pet.pet() + + pet.update() + + # OpenGL Rendering + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + draw_pet() + pygame.display.flip() + clock.tick(60) + + pygame.quit() + sys.exit() + + +if __name__ == "__main__": + main() diff --git a/main.py b/main.py new file mode 100644 index 0000000..e69de29 diff --git a/os_linux.py b/os_linux.py new file mode 100644 index 0000000..e69de29 diff --git a/os_windows.py b/os_windows.py new file mode 100644 index 0000000..e69de29 diff --git a/pets_sprites/demonite/demonite_float_correct.png b/pets_sprites/demonite/demonite_float_correct.png new file mode 100644 index 0000000000000000000000000000000000000000..65d8f48c79bc40e19377331c4d1fe1144c04f36b GIT binary patch literal 712 zcmV;(0yq7MP)@B4oukz zA%Q=E#qCMiK)#=Zz_PoC^?x_O#SX9$P23%g4sXBbv)JYNa7gVgmPaFq+nj)AV-R6T zqzeBo)lL-=5Qs!DgSlWsCp7P-rcyGrrqr|)LMOnQQnLhv^nr8SB4T6;RROi!uSEt) zrx5}ca1UfFwOS%10RadRwkkLkRujQNX&PuErhy|OibdQ=gQb?QkuJ4#()>{Yd=ZL^ zayGaiD&xu`kl>Uu2A9Fs!t_3uCNBvn;FyT&F~AGd(Qn~R3$=P$+(6o7TJKdUvWEx^ z-+(%1*3va{Kh;i%meEk#*W+<9{W+b`ZmQj^vrq(}@Y4UN8oEBG3E=PvR%=JaOcQ_t zp>`<>aSd$gxBvPsaIWuf|Gx&b1xwo{_&~0;r!qP%09?d~D&$Tfddpzh=te-(8FwvW zenp~PnXDPwqE#EEeK`xoKej4@!*fph+St5wO0y@4wJc=s z+7SHiTmpE+)`m~u)K6-2m(jJMd9*cua0IMdyf;lO{X!I=GV0mlEg(I%nSiZSB8wm@ z0J)8`j6KB`N}(Fb{^ex~sOF>)U`?az<+3RO;;wAm;-?B~Uf^@&em+sM=luWK*ep#JXTbgzHw8f*FMu0pi~Udy@ndGS6_8))>5q0qsvSqV%Z=0aUNnE$;=)xi}^pZz&`!Y8f*kAoMRBO0yx^v3G~;^c$X~a;c2h u%s5oQBBC}M_h;I5z4k?v4r$?u{r&*yVB57`16?%$00007o5fTTs8j=7Hd2NYQ?UAZ65+PPVXc?T9iu9<>5Fto9h#|!z5o9NEnGmyo zkSKs7(p(Q~agbfdmlEJ<9rI$fI&dP$Ddf$lm6XMvPoTn3Q`Bw8wzRXyT*(_o;wp7ma3XmOthS6`-$Ij%m{c z=Dv?Gw8zSvs}>@w&;p-99)v4mbq7IecZ<&WNFg}ihIr{1*Uj7+1SL%C&0IMh40p~G zl@RSrCY!xl#BDU|=}DuPip8~0!fORAc`pjWsaC=QmL|`MsP5(?r%O$H%(B6ia6b>s zBICiqi4L8GW*k4F7;XWYz{y5y=cz>U89$LV$QnW(GJtDchvXk@6bLhme@*H80OS8( s9S;SEybXvO8Ieh^y>#p0#mw}60Ba7&Z6QaAWB>pF07*qoM6N<$f*}nFE&u=k literal 0 HcmV?d00001 diff --git a/pets_sprites/demonite/demonite_idle2.png b/pets_sprites/demonite/demonite_idle2.png new file mode 100644 index 0000000000000000000000000000000000000000..d8230ef414de61c07d2977c6cefc68ff19c3ce39 GIT binary patch literal 581 zcmV-L0=oT)P)k=T5MMOk=xum!Rgd2Atz&!;>3l>?(5nrT*&{?LLOcC;#0JXLF=E<1OAr&^! z@blXW|Na`-#T66~Yu^OQ1fj114lu=&evim<#=7SsjwE`t@4Et{Git@tDF)gEGRdkzcin$fs+kt z=Y0U0-_Rh0JY+JiwUya-v>b>qmxyO*<15sL2f$(5gv4=L$fDO?yD9KuZWR9jrGBS44(#vloJ`M31ir*%!IxTT0%V2y^o#z1bXL0!U%>krTvgWdVP;wB<&rqcfeql6vJ9KZg{%Xo0cOaR+6)16;jt5w^=^zF6OA^z z2HArukD|yB=%KtMYiIbLhzJY=UfH%L zA|lW&_g>Dl3oQcObB`>RrUTI)j7*TNDMtu(D%GtPfx`%80qhwkfm(M#uTA>HM92bPkbD1ER>Kg;rtD52tK3oJ{;UJ_ggOiXZ@01j7su<#BoMp_9p!$w zWd>X5@?qNhI=$YSoQ5{pSgPM7Q2V22k|Z8@n0IUAJ&CI`U60mAlsAcK9VKY+bwbuP zV9lj&XsC6N1s`QdXI|HPZ)C9g^83vR%7T*Vxxzcw=_446$P?4 zE}8M(W#8(++2FO}=zP%{DFmpzmxj#bd!tt7YsGDG00FeacBp@(w0CMKYv9HKj|{71 qhOk8<+D7#Q0*(`)*pVgfgX|k0$-mnJnVP%+0000 z(KDE++BJYU_YjK}M*ta>=itMQVFd82mFLP?7rGEVfFZrxBVZr`(E`Y}&Wbo%4;sL3 z#EV9$j!-)=q6Qr>ZCFo_1W5P2mAdLcX7%X^Ax@=|rz-Gp8?{!%JDv;IAXckGFHXvCw}4*jT-EAl?6neYG*Gg^&c^HhbbwJ?IOKM}s*A70nYLi`ZP| z$pPsWdU^k-kiM>OFlMTSH1VXo^<1M$1%S)k8F&E9sA43?pXz{iLQMr|?KawTCVq3D zy8@u+??}6~SSfa}g;tl*{K5LwWVXm?zE<@q?*^buMQZWLR~!CMOfc!kYQ@Uk zZy4dDM42$!waywWMO8s63t5DC#8ZrA4zgCSEABpF07*qoM6N<$g7yjl?*IS* literal 0 HcmV?d00001 diff --git a/pets_sprites/demonite/heart_heart.png b/pets_sprites/demonite/heart_heart.png new file mode 100644 index 0000000000000000000000000000000000000000..ec3c7749f51753d26b618a4983c027f89719ceac GIT binary patch literal 1490 zcmV;@1ugoCP)iT&f;gl18^Bsg^x8dp9gc{Y3PCdg$Pnq1rTT|Tmlz_ z-?5B-_c*u?Ho;Pq-O8*-sI)_c_UGD}qPY+PDa34e0m-q4Za2_itN0Ka4xZVT{;K-zPim8A; zs~>L;E|G^GiI1p+0q=asD4qhTlnVR`U&Ee4rRO~Z+IUFZ$)YJx_VB&RY~We2EL@3M zBoD>5@r=Q|E9u!u%R&U1fqFPttlVw`?oap>-hjJcdL9C&KjIpu*)KEgKu!e^%oJ!Q zF};~F_JT!6i^SE_>-BIxoCPyM>%R}5!7FekTppCmM-SKThu5I5pgMr?M+AYcWHuWd z69nsGUJdI(H?-Q`4E8+7f;*wl&WEMY4lCePuzkpuR&_OF<|lX&-iDz}8fV;rVEMCo zaS>b$=5&SCGAeWhydH$PjoDnT!tv0J=E04i!WN^`Kv$ZP_M8G6s0wYc{sL=Y6?_b4 zvW)SQ&4sjf8(a#yPdrpY7xB6ZIxPQ5YpH}TrPWnHSBmqx0_%#h5!}rlMD1gL40?li zI9D%}O#y|rYt;XfhHi3VhI{FkHN^4Z!uQP2fql=>~X4W(S3O= zoou!EMg3F-C))72hMCEA4E%RsHCUx}N8P93^FX#bu$zV5Mn5o-VK6IrD!7AL%)jk( zZUPg4yHwD~rhqxT4<=GY<7(_Wu_64X!TcaBfybaVB#m(^kx_x}&6^bb3oxA(bXwxQ zonR%__v1`rU|$Gkv6I0>;DM*w6I?h)w>p5YWb~I{fWg@i|y!V#Nn0O@YRyrf6A5`^E(gbNgzi zSN{-%QUDL^=`LuHT18)mW#RW7%-4fWoX`8-S}>^N%wv3+Qv1PnL$}*CX$mY{xG-AR z-5ovC(Z22WdGmXxRaRN%J*4PP3gK+3)UvwF3*cegqu`y8-uq~6i&rPuCVU8=gB`0) zn|`w;SM}wYgCl1*+Sb+T0(9qBxKY$XZM}w^&w4yFxd$2Ry zcWnc9w17bz_ literal 0 HcmV?d00001 diff --git a/vpet.py b/vpet.py new file mode 100644 index 0000000..386cd8d --- /dev/null +++ b/vpet.py @@ -0,0 +1,121 @@ +from PIL import Image + + +class Rotate: + down = 0.0 + right = 90.0 + up = 180.0 + left = 270.0 + + +class Dialogs: + dreaming = [] + + +class Frames: + """These strings do match to the PNGs of the VPet which are representation the frames of an animation""" + idle_1 = 'idle1' + idle_2 = 'idle2' + idle_3 = 'idle3' + move_1 = 'moving_1' + move_2 = 'moving_2' + hearts_1 = 'heart' + floating = 'floating' + + +class Animations: + @staticmethod + def idle_1(rotation: float): + pass + + @staticmethod + def move_right(rotation: float): + pass + + @staticmethod + def move_left(rotation: float): + pass + + @staticmethod + def move_up(rotation: float): + pass + + @staticmethod + def move_down(rotation: float): + pass + + @staticmethod + def sleep(rotation: float): + pass + + @staticmethod + def wakeup(rotation: float): + pass + + +class VPet: + def __init__(self, x=0, y=0, screen=1): + self.screen = screen + self.x_postion = x + self.y_postion = y + self.rotation = Rotate.up + + self.is_idle = True + self.is_sleeping = False + self.is_dreaming = False + self.is_moving = False + self.is_talking = False + self.is_thinking = False + + def reset_activity_to_idle(self): + self.is_idle = True + self.is_sleeping = False + self.is_dreaming = False + self.is_moving = False + self.is_talking = False + self.is_thinking = False + + def choose_walk_destination(self): + """Decide, where the VPet should move to""" + pass + + def choose_activity(self): + """Decide, what kind of activity should be done""" + pass + + def move(self, x: int, y: int): + """Move the VPet to xy""" + self.is_moving = True + + def sleep(self): + """Set the VPet asleep""" + self.reset_activity_to_idle() + self.is_sleeping = True + Animations.sleep(rotation=self.rotation) + + def wakeup(self): + """Wake up the VPet from sleep""" + if not self.is_sleeping: return + Animations.wakeup(rotation=self.rotation) + self.reset_activity_to_idle() + + def blink_eyes(self): + """Make the VPet blink""" + pass + + def detect_orientation(self): + """Detect the desired rotation based on the postion on the screen, like left border, bottom, etc""" + pass + + def talk(self, message: str): + """Make the VPet talk to the user via a speech bubble""" + pass + + def think(self): + """Make the VPet think""" + pass + + def dream(self): + """Make the VPet dream""" + pass + diff --git a/window_qt.py b/window_qt.py new file mode 100755 index 0000000..576c3d0 --- /dev/null +++ b/window_qt.py @@ -0,0 +1,135 @@ +#!/bin/python3 + +import datetime +import time +import asyncio + +from PyQt5.QtWidgets import QOpenGLWidget +from PyQt5.QtWidgets import QMainWindow +from PyQt5.QtWidgets import QApplication +from PyQt5.QtCore import Qt +from OpenGL.GL import * +from OpenGL.GLU import gluOrtho2D +from PIL import Image + +from vpet import Rotate + + +class TransparentGLWidget(QOpenGLWidget): + def __init__(self, image_path): + super().__init__() + self.image_path = image_path + self.texture = None + self.image_width = 1 + self.image_height = 1 + + def initializeGL(self): + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glEnable(GL_DEPTH_TEST) + glClearColor(0.0, 0.0, 0.0, 0.0) + + # Textur init + self.texture, self.image_width, self.image_height = self.load_texture(self.image_path) + + def resizeGL(self, w, h): + glViewport(0, 0, w, h) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluOrtho2D(0, w, 0, h) # Orthografische Projektion für 2D + glMatrixMode(GL_MODELVIEW) + + def paintGL(self): + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + glPushMatrix() # Matrix speichern + self.draw_textured_quad() + glPopMatrix() # Matrix wiederherstellen + + def load_texture(self, image_path): + img = Image.open(image_path).convert("RGBA") + img = img.rotate(angle=Rotate.up) + img_data = img.tobytes() + width, height = img.size + + # Create OpenGL texture + texture_id = glGenTextures(1) + glBindTexture(GL_TEXTURE_2D, texture_id) + + # Set texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + + # Load texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data) + return texture_id, width, height + + def draw_textured_quad(self): + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + + width, height = self.width(), self.height() + + glBegin(GL_QUADS) + glTexCoord2f(0.0, 0.0) + glVertex2f(0, 0) # Down left corner + glTexCoord2f(1.0, 0.0) + glVertex2f(width, 0) # Down right corner + glTexCoord2f(1.0, 1.0) + glVertex2f(width, height) # Up right corner + glTexCoord2f(0.0, 1.0) + glVertex2f(0, height) # Up left corner + glEnd() + + glDisable(GL_TEXTURE_2D) + + +class MainWindow(QMainWindow): + def __init__(self, image_path): + super().__init__() + # self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + self.setWindowFlags(Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setCentralWidget(TransparentGLWidget(image_path)) + self.click_event_time = 0 + + img = Image.open(image_path) + self.resize(img.width, img.height) + + async def loop(self): + self.move(0, 0) + self.show() + print('Loop') + + def mousePressEvent(self, event): + self.click_event_time = time.time() + if event.button() == Qt.LeftButton: + self.drag_position = event.globalPos() + + def mouseReleaseEvent(self, event): + time_delta = time.time() - self.click_event_time + if time_delta <= 0.2: + if event.button() == Qt.LeftButton: + print('Click left') + if event.button() == Qt.RightButton: + print('Click right') + self.click_event_time = 0 + + def mouseMoveEvent(self, event): + self.click_event_time = 0 + if event.buttons() == Qt.LeftButton: + delta = event.globalPos() - self.drag_position + self.move(self.pos() + delta) + self.drag_position = event.globalPos() + + +async def start(): + app = QApplication([]) + image_path = 'pets_sprites/demonite/demonite_idle1.png' # Ersetze dies mit deinem Bildpfad + window = MainWindow(image_path) + await window.loop() + app.exec_() + +asyncio.run(start())