mirror of
https://github.com/bdnugget/Pixel-Editor.git
synced 2026-11-07 09:17:36 +00:00
Initial project setup with Docker support, Go modules, and a basic pixel art editor.
This commit is contained in:
1 parent
ced23f8091
commit
33add40f66
24 files changed
+1320
-1
No files matched your search
@@ -0,0 +1,5 @@
|
||||
.git
|
||||
.idea
|
||||
.vscode
|
||||
*.md
|
||||
!README.md
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Build files
|
||||
bin/
|
||||
dist/
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
FROM golang:1.22-alpine AS builder
|
||||
|
||||
# Install required system dependencies for Fyne
|
||||
RUN apk add --no-cache gcc g++ musl-dev xorg-server-dev \
|
||||
libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev \
|
||||
libxi-dev mesa-dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files first for better caching
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy the rest of the application
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN go build -o pixeleditor ./src/pixel.go
|
||||
|
||||
# Use a lightweight Alpine image for the final image
|
||||
FROM alpine:latest
|
||||
|
||||
# Install required runtime dependencies for Fyne applications
|
||||
RUN apk add --no-cache xorg-server mesa-gl mesa-dri-gallium \
|
||||
libx11 libxcursor libxrandr libxinerama libxi \
|
||||
dbus-x11 ttf-dejavu
|
||||
|
||||
# Set environment variables for X11
|
||||
ENV DISPLAY=:0
|
||||
ENV XAUTHORITY=/tmp/.Xauthority
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the built executable from the builder stage
|
||||
COPY --from=builder /app/pixeleditor .
|
||||
|
||||
# Set the entry point
|
||||
ENTRYPOINT ["./pixeleditor"]
|
||||
@@ -1 +1,66 @@
|
||||
# Pixel-Editor
|
||||
# PixelEditor
|
||||
|
||||
[](https://fyne.io/)
|
||||
[](LICENSE)
|
||||
|
||||
A simple pixel art editor built with Go and the Fyne UI framework.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pixel Canvas**: 50x50 grid for creating pixel art
|
||||
- **Color Selection**: RGB color picker
|
||||
- **Brush Tools**: Basic brush functionality
|
||||
- **Pan & Navigate**: Move around the canvas
|
||||
- **Simple UI**: Clean interface with essential controls
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.22 or higher
|
||||
- Fyne dependencies (see [Fyne Getting Started](https://developer.fyne.io/started/))
|
||||
|
||||
### Option 1: Run Natively (Recommended)
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/abdul756/Pixel-Editor.git
|
||||
cd Pixel-Editor
|
||||
|
||||
# Run the helper script (will install dependencies if needed)
|
||||
./run-native.sh
|
||||
```
|
||||
|
||||
### Option 2: Using Docker
|
||||
|
||||
Make sure Docker is installed and running on your system.
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/abdul756/Pixel-Editor.git
|
||||
cd Pixel-Editor
|
||||
|
||||
# Run the helper script
|
||||
./run.sh
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Start the Application**: Launch PixelEditor
|
||||
2. **Draw**: Click and drag on the canvas to draw pixels
|
||||
3. **Change Colors**: Use the color picker to select colors
|
||||
4. **Navigate**: Right-click and drag to pan around the canvas
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **src/core**: Canvas rendering and pixel manipulation
|
||||
- **src/ui**: User interface components
|
||||
- **src/utils**: Utility functions and types
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
Built with Go and Fyne
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
pixeleditor:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- DISPLAY=${DISPLAY}
|
||||
- XAUTHORITY=/tmp/.Xauthority
|
||||
volumes:
|
||||
- /tmp/.X11-unix:/tmp/.X11-unix
|
||||
- ~/.Xauthority:/tmp/.Xauthority:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # System Docker socket
|
||||
- ~/.docker/desktop/docker.sock:/var/run/docker.sock:ro # Docker Desktop socket (fallback)
|
||||
network_mode: host
|
||||
privileged: true # Required for some graphics drivers
|
||||
# For X11 forwarding to work, run these commands before starting:
|
||||
# xhost +local:docker
|
||||
@@ -0,0 +1,45 @@
|
||||
module github.com/abdul756/Pixel-Editor
|
||||
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.23.8
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.6.0
|
||||
github.com/lusingander/colorpicker v0.7.4
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.11.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fredbi/uri v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.1.0 // indirect
|
||||
github.com/fyne-io/glfw-js v0.2.0 // indirect
|
||||
github.com/fyne-io/image v0.1.1 // indirect
|
||||
github.com/fyne-io/oksvg v0.1.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.2.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rymdport/portal v0.4.1 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
fyne.io/fyne/v2 v2.6.0 h1:Rywo9yKYN4qvNuvkRuLF+zxhJYWbIFM+m4N4KV4p1pQ=
|
||||
fyne.io/fyne/v2 v2.6.0/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
|
||||
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
||||
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
|
||||
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
|
||||
github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||
github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM=
|
||||
github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||
github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
|
||||
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||
github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
|
||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lusingander/colorpicker v0.7.4 h1:nmWIZMEXofbFivR0q9lyBojEsJgmGgHvhX4/mQlkJqo=
|
||||
github.com/lusingander/colorpicker v0.7.4/go.mod h1:rPrV3txoQlu9LZHTwZUWf0qqc8dSMATE0mZ1K4gx6Uc=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
|
||||
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if Go is installed
|
||||
if ! command -v go > /dev/null 2>&1; then
|
||||
echo "Error: Go is not installed. Please install Go 1.22 or later."
|
||||
echo "Visit https://go.dev/doc/install for installation instructions."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if required dependencies are installed
|
||||
echo "Checking dependencies..."
|
||||
if command -v apt-get > /dev/null 2>&1; then
|
||||
# For Debian/Ubuntu
|
||||
echo "This system uses apt package manager."
|
||||
echo "You may need these packages: libgl1-mesa-dev xorg-dev"
|
||||
echo "sudo apt-get install libgl1-mesa-dev xorg-dev"
|
||||
elif command -v pacman > /dev/null 2>&1; then
|
||||
# For Arch Linux
|
||||
echo "This system uses pacman package manager."
|
||||
echo "You may need these packages: libgl mesa xorg-server-devel"
|
||||
echo "sudo pacman -S libgl mesa xorg-server-devel"
|
||||
fi
|
||||
|
||||
# Build and run
|
||||
echo "Building and running PixelEditor..."
|
||||
cd "$(dirname "$0")"
|
||||
go run src/pixel.go
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to check Docker status
|
||||
check_docker() {
|
||||
# Try standard socket first
|
||||
if docker info > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If that fails, look for Docker Desktop socket
|
||||
if [ -S /var/run/docker.sock ]; then
|
||||
export DOCKER_HOST=unix:///var/run/docker.sock
|
||||
if docker info > /dev/null 2>&1; then
|
||||
echo "Using system Docker socket."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for Docker Desktop socket in home directory
|
||||
if [ -S "$HOME/.docker/desktop/docker.sock" ]; then
|
||||
export DOCKER_HOST=unix://$HOME/.docker/desktop/docker.sock
|
||||
if docker info > /dev/null 2>&1; then
|
||||
echo "Using Docker Desktop socket."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Docker is not running
|
||||
echo "Error: Docker daemon is not running."
|
||||
echo "Please start Docker with one of these commands:"
|
||||
echo " • systemctl start docker (for systemd systems)"
|
||||
echo " • sudo service docker start (for init.d systems)"
|
||||
echo " • Open Docker Desktop if you're using that application"
|
||||
|
||||
echo -e "\nWould you like to try running the application natively instead? (y/n)"
|
||||
read -r choice
|
||||
if [[ "$choice" =~ ^[Yy]$ ]]; then
|
||||
echo "Running application natively..."
|
||||
./run-native.sh
|
||||
exit $?
|
||||
fi
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if Docker daemon is running
|
||||
check_docker
|
||||
|
||||
# Allow local Docker containers to access X server
|
||||
if ! command -v xhost > /dev/null 2>&1; then
|
||||
echo "Error: xhost command not found. Please install X11 utilities package."
|
||||
echo " • sudo apt-get install x11-xserver-utils"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
xhost +local:docker
|
||||
|
||||
# Build and run the Docker container
|
||||
echo "Building and starting the container..."
|
||||
docker-compose up --build
|
||||
|
||||
# Clean up X server permissions when done
|
||||
xhost -local:docker
|
||||
@@ -0,0 +1,76 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/abdul756/Pixel-Editor/src/utils"
|
||||
)
|
||||
|
||||
// Default background color - light gray
|
||||
var DefaultBackgroundColor = color.NRGBA{128, 128, 128, 255}
|
||||
|
||||
// Brushable interface implementation
|
||||
func (pxCanvas *PxCanvas) SetColor(c color.Color, x, y int) {
|
||||
if rgba, ok := pxCanvas.PixelData.(*image.RGBA); ok {
|
||||
if x >= 0 && y >= 0 && x < pxCanvas.PxCols && y < pxCanvas.PxRows {
|
||||
rgba.Set(x, y, c)
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PaintSwatch paints at the given position using the current brush
|
||||
func (pxCanvas *PxCanvas) PaintSwatch(p fyne.Position) {
|
||||
// Convert from canvas coordinates to pixel coordinates
|
||||
pixelX, pixelY := pxCanvas.CanvasToPixel(p)
|
||||
|
||||
// Apply brushing based on brush type
|
||||
switch pxCanvas.appState.BrushType {
|
||||
case utils.BrushTypePen:
|
||||
pxCanvas.paintPixel(pixelX, pixelY)
|
||||
case utils.BrushTypeEraser:
|
||||
pxCanvas.paintEraser(pixelX, pixelY)
|
||||
}
|
||||
}
|
||||
|
||||
// CanvasToPixel converts from canvas position to pixel position
|
||||
// This is a critical function for cursor position accuracy
|
||||
func (pxCanvas *PxCanvas) CanvasToPixel(p fyne.Position) (int, int) {
|
||||
// Calculate the position relative to the canvas offset
|
||||
relativeX := p.X - pxCanvas.CanvasOffset.X
|
||||
relativeY := p.Y - pxCanvas.CanvasOffset.Y
|
||||
|
||||
// Convert to pixel coordinates, ensuring we use integer division
|
||||
// to get the exact pixel index
|
||||
pixelX := int(relativeX) / pxCanvas.PxSize
|
||||
pixelY := int(relativeY) / pxCanvas.PxSize
|
||||
|
||||
return pixelX, pixelY
|
||||
}
|
||||
|
||||
// paintPixel paints a single pixel at the given position
|
||||
func (pxCanvas *PxCanvas) paintPixel(x, y int) {
|
||||
// Check if the pixel is within bounds
|
||||
if x >= 0 && y >= 0 && x < pxCanvas.PxCols && y < pxCanvas.PxRows {
|
||||
pxCanvas.SetColor(pxCanvas.appState.BrushColor, x, y)
|
||||
pxCanvas.reloadImage = true
|
||||
}
|
||||
}
|
||||
|
||||
// paintEraser erases at the given position (resets to default background color)
|
||||
func (pxCanvas *PxCanvas) paintEraser(x, y int) {
|
||||
if x >= 0 && y >= 0 && x < pxCanvas.PxCols && y < pxCanvas.PxRows {
|
||||
pxCanvas.SetColor(DefaultBackgroundColor, x, y)
|
||||
pxCanvas.reloadImage = true
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for absolute value (used by mouse dragging)
|
||||
func abs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
)
|
||||
|
||||
// Pan moves the canvas based on mouse movement
|
||||
// This is the core function handling panning operation
|
||||
func (pxCanvas *PxCanvas) Pan(previousCoord *fyne.PointEvent, ev *desktop.MouseEvent) {
|
||||
if previousCoord == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate movement delta directly from mouse positions
|
||||
deltaX := ev.Position.X - previousCoord.Position.X
|
||||
deltaY := ev.Position.Y - previousCoord.Position.Y
|
||||
|
||||
fmt.Printf("Panning: deltaX=%f, deltaY=%f\n", deltaX, deltaY)
|
||||
|
||||
// Apply the movement directly to the canvas offset
|
||||
pxCanvas.CanvasOffset = pxCanvas.CanvasOffset.Add(fyne.NewPos(deltaX, deltaY))
|
||||
|
||||
// Force layout update and refresh
|
||||
if pxCanvas.renderer != nil {
|
||||
pxCanvas.renderer.Layout(pxCanvas.Size())
|
||||
}
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
|
||||
// Scale changes the pixel size for zooming
|
||||
// This function handles both zooming in and zooming out
|
||||
func (pxCanvas *PxCanvas) Scale(direction int) {
|
||||
// Get current pixel size
|
||||
currentSize := pxCanvas.PxSize
|
||||
newSize := currentSize
|
||||
|
||||
// Calculate new size based on zoom direction
|
||||
if direction > 0 {
|
||||
// Zoom in - larger pixels
|
||||
newSize = currentSize + 1
|
||||
if newSize > 40 {
|
||||
newSize = 40 // Maximum zoom level
|
||||
}
|
||||
} else if direction < 0 {
|
||||
// Zoom out - smaller pixels
|
||||
newSize = currentSize - 1
|
||||
if newSize < 2 {
|
||||
newSize = 2 // Minimum zoom level
|
||||
}
|
||||
}
|
||||
|
||||
// Only proceed if the size actually changed
|
||||
if newSize != currentSize {
|
||||
fmt.Printf("Zooming: %d -> %d\n", currentSize, newSize)
|
||||
|
||||
// Store the old dimensions
|
||||
oldWidth := float32(pxCanvas.PxCols * currentSize)
|
||||
oldHeight := float32(pxCanvas.PxRows * currentSize)
|
||||
|
||||
// Update the pixel size
|
||||
pxCanvas.PxSize = newSize
|
||||
|
||||
// Calculate new dimensions
|
||||
newWidth := float32(pxCanvas.PxCols * newSize)
|
||||
newHeight := float32(pxCanvas.PxRows * newSize)
|
||||
|
||||
// Adjust offset to zoom toward center
|
||||
widthDiff := newWidth - oldWidth
|
||||
heightDiff := newHeight - oldHeight
|
||||
|
||||
// Move toward center of canvas
|
||||
pxCanvas.CanvasOffset = pxCanvas.CanvasOffset.SubtractXY(
|
||||
widthDiff/2,
|
||||
heightDiff/2,
|
||||
)
|
||||
|
||||
// Force layout update and refresh
|
||||
if pxCanvas.renderer != nil {
|
||||
pxCanvas.renderer.Layout(pxCanvas.Size())
|
||||
}
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// PixelToCanvas converts a pixel position to a canvas position
|
||||
func (pxCanvas *PxCanvas) PixelToCanvas(x, y int) fyne.Position {
|
||||
// Convert pixel coordinates to canvas coordinates
|
||||
canvasX := float32(x*pxCanvas.PxSize) + pxCanvas.CanvasOffset.X
|
||||
canvasY := float32(y*pxCanvas.PxSize) + pxCanvas.CanvasOffset.Y
|
||||
|
||||
return fyne.NewPos(canvasX, canvasY)
|
||||
}
|
||||
|
||||
// Creates a new RGBA image from the canvas, starting at the given bounds
|
||||
func (pxCanvas *PxCanvas) PixelSelection(topLeft, bottomRight *image.Point) *image.RGBA {
|
||||
bounds := image.Rectangle{
|
||||
Min: *topLeft,
|
||||
Max: *bottomRight,
|
||||
}
|
||||
img := image.NewRGBA(bounds)
|
||||
// Copy of the pixels in the bounds
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
if srcImg, ok := pxCanvas.PixelData.(*image.RGBA); ok {
|
||||
img.Set(x, y, srcImg.At(x, y))
|
||||
}
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// Utility function to get mouse events relative to the canvas
|
||||
func (pxCanvas *PxCanvas) MouseToCanvasXY(ev *desktop.MouseEvent) (int, int) {
|
||||
bounds := pxCanvas.Bounds()
|
||||
return int(ev.Position.X) - bounds.Min.X, int(ev.Position.Y) - bounds.Min.Y
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
)
|
||||
|
||||
// Scrolled handles mouse wheel events for zooming
|
||||
func (pxCanvas *PxCanvas) Scrolled(event *fyne.ScrollEvent) {
|
||||
// Call Scale with the scroll direction for zooming
|
||||
pxCanvas.Scale(int(event.Scrolled.DY))
|
||||
}
|
||||
|
||||
// MouseDown handles the mouse down event
|
||||
func (pxCanvas *PxCanvas) MouseDown(ev *desktop.MouseEvent) {
|
||||
// Store position for later use
|
||||
pxCanvas.mouseState.previousCoord = &fyne.PointEvent{
|
||||
Position: ev.Position,
|
||||
AbsolutePosition: ev.AbsolutePosition,
|
||||
}
|
||||
|
||||
// Set dragging state
|
||||
pxCanvas.mouseState.isDragging = true
|
||||
|
||||
// Only paint with left button
|
||||
if ev.Button == desktop.MouseButtonPrimary {
|
||||
pixelX, pixelY := pxCanvas.CanvasToPixel(ev.Position)
|
||||
// Check if click is within canvas
|
||||
if pixelX >= 0 && pixelY >= 0 && pixelX < pxCanvas.PxCols && pixelY < pxCanvas.PxRows {
|
||||
pxCanvas.PaintSwatch(ev.Position)
|
||||
}
|
||||
}
|
||||
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
|
||||
// MouseUp handles the mouse up event
|
||||
func (pxCanvas *PxCanvas) MouseUp(ev *desktop.MouseEvent) {
|
||||
// Clear dragging state
|
||||
pxCanvas.mouseState.isDragging = false
|
||||
pxCanvas.mouseState.previousCoord = nil
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
|
||||
// MouseMoved handles the mouse moved event
|
||||
func (pxCanvas *PxCanvas) MouseMoved(ev *desktop.MouseEvent) {
|
||||
// Always update cursor position
|
||||
pxCanvas.cursorPos = ev.Position
|
||||
|
||||
// If not dragging, just update cursor
|
||||
if !pxCanvas.mouseState.isDragging || pxCanvas.mouseState.previousCoord == nil {
|
||||
pxCanvas.Refresh()
|
||||
return
|
||||
}
|
||||
|
||||
// Handle based on mouse button
|
||||
if ev.Button == desktop.MouseButtonPrimary {
|
||||
// Draw with left button if within canvas
|
||||
pixelX, pixelY := pxCanvas.CanvasToPixel(ev.Position)
|
||||
if pixelX >= 0 && pixelY >= 0 && pixelX < pxCanvas.PxCols && pixelY < pxCanvas.PxRows {
|
||||
pxCanvas.PaintSwatch(ev.Position)
|
||||
}
|
||||
} else if ev.Button == desktop.MouseButtonSecondary {
|
||||
// Pan with right button - direct call to Pan
|
||||
pxCanvas.Pan(pxCanvas.mouseState.previousCoord, ev)
|
||||
}
|
||||
|
||||
// Update previous position for next move
|
||||
pxCanvas.mouseState.previousCoord = &fyne.PointEvent{
|
||||
Position: ev.Position,
|
||||
AbsolutePosition: ev.AbsolutePosition,
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Cursor for the desktop.Cursor interface
|
||||
func (pxCanvas *PxCanvas) Cursor() desktop.Cursor {
|
||||
return desktop.DefaultCursor
|
||||
}
|
||||
|
||||
func (pxCanvas *PxCanvas) MouseIn(ev *desktop.MouseEvent) {
|
||||
pxCanvas.cursorPos = ev.Position
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
|
||||
func (pxCanvas *PxCanvas) MouseOut() {
|
||||
pxCanvas.cursorPos = fyne.Position{X: -1, Y: -1}
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
|
||||
// GetCursorPixel returns the pixel coordinates under cursor
|
||||
func (pxCanvas *PxCanvas) GetCursorPixel() (int, int) {
|
||||
return pxCanvas.CanvasToPixel(pxCanvas.cursorPos)
|
||||
}
|
||||
|
||||
// Dragged implements the Draggable interface
|
||||
func (pxCanvas *PxCanvas) Dragged(event *fyne.DragEvent) {
|
||||
// Direct panning with drag event
|
||||
pxCanvas.CanvasOffset = pxCanvas.CanvasOffset.Add(event.Dragged)
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
|
||||
// DragEnd implements the Draggable interface
|
||||
func (pxCanvas *PxCanvas) DragEnd() {
|
||||
// Nothing needed here
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/abdul756/Pixel-Editor/src/utils"
|
||||
)
|
||||
|
||||
type PxCanvasMouseState struct {
|
||||
previousCoord *fyne.PointEvent
|
||||
isDragging bool
|
||||
}
|
||||
|
||||
type PxCanvas struct {
|
||||
widget.BaseWidget
|
||||
utils.PxCanvasConfig
|
||||
renderer *PxCanvasRenderer
|
||||
mouseState PxCanvasMouseState
|
||||
PixelData image.Image
|
||||
appState *utils.State
|
||||
reloadImage bool
|
||||
cursorPos fyne.Position // Track cursor position for highlighting
|
||||
|
||||
// Draggable state
|
||||
dragging bool
|
||||
dragStarted bool
|
||||
dragPos fyne.Position
|
||||
}
|
||||
|
||||
// Ensure PxCanvas implements fyne.Draggable and apptype.Brushable
|
||||
var _ fyne.Draggable = (*PxCanvas)(nil)
|
||||
var _ utils.Brushable = (*PxCanvas)(nil)
|
||||
|
||||
// Ensure PxCanvas implements the desktop mouse interfaces
|
||||
var _ desktop.Mouseable = (*PxCanvas)(nil)
|
||||
var _ desktop.Hoverable = (*PxCanvas)(nil)
|
||||
|
||||
// No other interface declarations to avoid conflicts with our custom mouse handling
|
||||
|
||||
func (pxCanvas *PxCanvas) Bounds() image.Rectangle {
|
||||
x0 := int(pxCanvas.CanvasOffset.X)
|
||||
y0 := int(pxCanvas.CanvasOffset.Y)
|
||||
x1 := int(pxCanvas.PxCols*pxCanvas.PxSize + int(pxCanvas.CanvasOffset.X))
|
||||
y1 := int(pxCanvas.PxRows*pxCanvas.PxSize + int(pxCanvas.CanvasOffset.Y))
|
||||
return image.Rect(x0, y0, x1, y1)
|
||||
}
|
||||
|
||||
func InBounds(pos fyne.Position, bounds image.Rectangle) bool {
|
||||
return float32(bounds.Min.X) <= pos.X && pos.X < float32(bounds.Max.X) &&
|
||||
float32(bounds.Min.Y) <= pos.Y && pos.Y < float32(bounds.Max.Y)
|
||||
}
|
||||
|
||||
func NewBlankImage(cols, rows int, c color.Color) *image.RGBA {
|
||||
img := image.NewRGBA(image.Rect(0, 0, cols, rows))
|
||||
for i := 0; i < rows; i++ {
|
||||
for j := 0; j < cols; j++ {
|
||||
img.Set(j, i, c)
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func NewPxCanvas(state *utils.State, config utils.PxCanvasConfig) *PxCanvas {
|
||||
pxCanvas := &PxCanvas{
|
||||
PxCanvasConfig: config,
|
||||
appState: state,
|
||||
mouseState: PxCanvasMouseState{},
|
||||
}
|
||||
// Set the initial canvas color to gray
|
||||
pxCanvas.PixelData = NewBlankImage(config.PxCols, config.PxRows, color.NRGBA{128, 128, 128, 255})
|
||||
pxCanvas.ExtendBaseWidget(pxCanvas)
|
||||
return pxCanvas
|
||||
}
|
||||
|
||||
func (pxCanvas *PxCanvas) CreateRenderer() fyne.WidgetRenderer {
|
||||
canvasImage := canvas.NewImageFromImage(pxCanvas.PixelData)
|
||||
canvasImage.ScaleMode = canvas.ImageScalePixels
|
||||
canvasImage.FillMode = canvas.ImageFillContain
|
||||
canvasImage.Show()
|
||||
|
||||
canvasBorder := make([]canvas.Line, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
canvasBorder[i].StrokeColor = color.NRGBA{0, 0, 0, 255}
|
||||
canvasBorder[i].StrokeWidth = 2
|
||||
}
|
||||
|
||||
// Initialize cursor rectangle with default values
|
||||
cursorRect := &canvas.Rectangle{
|
||||
StrokeColor: color.NRGBA{255, 0, 0, 255}, // Bright red border
|
||||
StrokeWidth: 4, // Extra thick border
|
||||
FillColor: color.NRGBA{255, 255, 0, 150}, // More opaque yellow fill
|
||||
}
|
||||
// Hide it initially until mouse moves over canvas
|
||||
cursorRect.Hide()
|
||||
|
||||
renderer := &PxCanvasRenderer{
|
||||
pxCanvas: pxCanvas,
|
||||
canvasImage: canvasImage,
|
||||
canvasBorder: canvasBorder,
|
||||
cursorRect: cursorRect,
|
||||
}
|
||||
|
||||
pxCanvas.renderer = renderer
|
||||
return renderer
|
||||
}
|
||||
|
||||
func (pxCanvas *PxCanvas) TryPan(previousCoord *fyne.PointEvent, currentCoord *desktop.MouseEvent) {
|
||||
if previousCoord != nil && currentCoord.Button == desktop.MouseButtonSecondary {
|
||||
pxCanvas.Pan(previousCoord, currentCoord)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a method to directly update the canvas position and trigger a refresh
|
||||
func (pxCanvas *PxCanvas) SetOffset(offset fyne.Position) {
|
||||
pxCanvas.CanvasOffset = offset
|
||||
if pxCanvas.renderer != nil {
|
||||
pxCanvas.renderer.Layout(pxCanvas.Size())
|
||||
pxCanvas.Refresh()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
)
|
||||
|
||||
type PxCanvasRenderer struct {
|
||||
pxCanvas *PxCanvas
|
||||
canvasImage *canvas.Image
|
||||
canvasBorder []canvas.Line
|
||||
cursorRect *canvas.Rectangle
|
||||
}
|
||||
|
||||
func (renderer *PxCanvasRenderer) MinSize() fyne.Size {
|
||||
return renderer.pxCanvas.DrawingArea
|
||||
}
|
||||
|
||||
func (renderer *PxCanvasRenderer) Objects() []fyne.CanvasObject {
|
||||
// Create a slice including the canvas image, border lines, and cursor rect
|
||||
objects := make([]fyne.CanvasObject, 0, 6)
|
||||
|
||||
// Add canvas image
|
||||
objects = append(objects, renderer.canvasImage)
|
||||
|
||||
// Add 4 border lines
|
||||
for i := 0; i < 4; i++ {
|
||||
objects = append(objects, &renderer.canvasBorder[i])
|
||||
}
|
||||
|
||||
// Add cursor rect
|
||||
objects = append(objects, renderer.cursorRect)
|
||||
|
||||
return objects
|
||||
}
|
||||
|
||||
func (renderer *PxCanvasRenderer) Destroy() {}
|
||||
|
||||
func (renderer *PxCanvasRenderer) Layout(size fyne.Size) {
|
||||
// Position and size the canvas image based on pixel size and offset
|
||||
renderer.canvasImage.Move(renderer.pxCanvas.CanvasOffset)
|
||||
renderer.canvasImage.Resize(fyne.NewSize(
|
||||
float32(renderer.pxCanvas.PxCols*renderer.pxCanvas.PxSize),
|
||||
float32(renderer.pxCanvas.PxRows*renderer.pxCanvas.PxSize),
|
||||
))
|
||||
|
||||
// Update border to surround the canvas
|
||||
renderer.updateBorder()
|
||||
|
||||
// Update the cursor position
|
||||
renderer.updateCursor()
|
||||
}
|
||||
|
||||
func (renderer *PxCanvasRenderer) Refresh() {
|
||||
// Update image if needed
|
||||
if renderer.pxCanvas.reloadImage {
|
||||
renderer.canvasImage.Image = renderer.pxCanvas.PixelData
|
||||
renderer.canvasImage.Refresh()
|
||||
renderer.pxCanvas.reloadImage = false
|
||||
}
|
||||
|
||||
// Update border position
|
||||
renderer.updateBorder()
|
||||
|
||||
// Update cursor position
|
||||
renderer.updateCursor()
|
||||
|
||||
// Refresh all elements
|
||||
renderer.canvasImage.Refresh()
|
||||
for i := 0; i < 4; i++ {
|
||||
renderer.canvasBorder[i].Refresh()
|
||||
}
|
||||
renderer.cursorRect.Refresh()
|
||||
}
|
||||
|
||||
// updateBorder sets the positions of the border lines
|
||||
func (renderer *PxCanvasRenderer) updateBorder() {
|
||||
offset := renderer.pxCanvas.CanvasOffset
|
||||
width := float32(renderer.pxCanvas.PxCols * renderer.pxCanvas.PxSize)
|
||||
height := float32(renderer.pxCanvas.PxRows * renderer.pxCanvas.PxSize)
|
||||
|
||||
// Top line
|
||||
renderer.canvasBorder[0].Position1 = fyne.NewPos(offset.X, offset.Y)
|
||||
renderer.canvasBorder[0].Position2 = fyne.NewPos(offset.X+width, offset.Y)
|
||||
|
||||
// Right line
|
||||
renderer.canvasBorder[1].Position1 = fyne.NewPos(offset.X+width, offset.Y)
|
||||
renderer.canvasBorder[1].Position2 = fyne.NewPos(offset.X+width, offset.Y+height)
|
||||
|
||||
// Bottom line
|
||||
renderer.canvasBorder[2].Position1 = fyne.NewPos(offset.X, offset.Y+height)
|
||||
renderer.canvasBorder[2].Position2 = fyne.NewPos(offset.X+width, offset.Y+height)
|
||||
|
||||
// Left line
|
||||
renderer.canvasBorder[3].Position1 = fyne.NewPos(offset.X, offset.Y)
|
||||
renderer.canvasBorder[3].Position2 = fyne.NewPos(offset.X, offset.Y+height)
|
||||
}
|
||||
|
||||
// updateCursor positions the cursor highlight over the correct pixel
|
||||
func (renderer *PxCanvasRenderer) updateCursor() {
|
||||
// Get cursor position
|
||||
cursorPos := renderer.pxCanvas.cursorPos
|
||||
|
||||
// Hide cursor if outside canvas
|
||||
if cursorPos.X < 0 || cursorPos.Y < 0 {
|
||||
renderer.cursorRect.Hide()
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to pixel coordinates
|
||||
pixelX, pixelY := renderer.pxCanvas.CanvasToPixel(cursorPos)
|
||||
|
||||
// Check if pixel is within bounds
|
||||
if pixelX < 0 || pixelY < 0 || pixelX >= renderer.pxCanvas.PxCols || pixelY >= renderer.pxCanvas.PxRows {
|
||||
renderer.cursorRect.Hide()
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate position in canvas coordinates
|
||||
pixelSize := float32(renderer.pxCanvas.PxSize)
|
||||
xPos := renderer.pxCanvas.CanvasOffset.X + float32(pixelX)*pixelSize
|
||||
yPos := renderer.pxCanvas.CanvasOffset.Y + float32(pixelY)*pixelSize
|
||||
|
||||
// Position and size the cursor rectangle
|
||||
renderer.cursorRect.Move(fyne.NewPos(xPos, yPos))
|
||||
renderer.cursorRect.Resize(fyne.NewSize(pixelSize, pixelSize))
|
||||
|
||||
// Set cursor appearance
|
||||
renderer.cursorRect.StrokeWidth = 2
|
||||
renderer.cursorRect.StrokeColor = color.NRGBA{255, 0, 0, 255} // Red border
|
||||
renderer.cursorRect.FillColor = color.NRGBA{255, 255, 0, 100} // Semi-transparent yellow
|
||||
|
||||
// Make cursor visible
|
||||
renderer.cursorRect.Show()
|
||||
}
|
||||
|
||||
// UpdateCursor is called when the mouse moves
|
||||
func (renderer *PxCanvasRenderer) UpdateCursor(ev *desktop.MouseEvent) {
|
||||
if ev == nil {
|
||||
// Use stored cursor position if event is nil
|
||||
renderer.updateCursor()
|
||||
return
|
||||
}
|
||||
|
||||
// Update with the new cursor position
|
||||
renderer.pxCanvas.cursorPos = ev.Position
|
||||
renderer.updateCursor()
|
||||
}
|
||||
|
||||
// newPxCanvasRenderer creates a new canvas renderer for a px canvas
|
||||
func newPxCanvasRenderer(pxCanvas *PxCanvas) *PxCanvasRenderer {
|
||||
// Create a more visible cursor with a contrasting stroke and fill
|
||||
cursorRect := canvas.NewRectangle(color.NRGBA{R: 255, G: 255, B: 0, A: 100})
|
||||
cursorRect.StrokeWidth = 3
|
||||
cursorRect.StrokeColor = color.NRGBA{R: 255, G: 0, B: 0, A: 255} // Bright red stroke
|
||||
cursorRect.FillColor = color.NRGBA{R: 255, G: 255, B: 0, A: 100} // Semi-transparent yellow fill
|
||||
|
||||
// Hide cursor initially
|
||||
cursorRect.Hide()
|
||||
|
||||
// Create the border lines
|
||||
canvasBorder := make([]canvas.Line, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
canvasBorder[i].StrokeColor = color.RGBA{50, 50, 50, 255}
|
||||
canvasBorder[i].StrokeWidth = 2
|
||||
}
|
||||
|
||||
renderer := &PxCanvasRenderer{
|
||||
pxCanvas: pxCanvas,
|
||||
canvasImage: canvas.NewImageFromImage(pxCanvas.PixelData),
|
||||
canvasBorder: canvasBorder,
|
||||
cursorRect: cursorRect,
|
||||
}
|
||||
|
||||
renderer.canvasImage.ScaleMode = canvas.ImageScalePixels
|
||||
renderer.canvasImage.FillMode = canvas.ImageFillContain
|
||||
|
||||
return renderer
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"github.com/abdul756/Pixel-Editor/src/core"
|
||||
"github.com/abdul756/Pixel-Editor/src/ui"
|
||||
"github.com/abdul756/Pixel-Editor/src/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pixelApp := app.New()
|
||||
pixelWindow := pixelApp.NewWindow("PixelMaster")
|
||||
|
||||
// Set a reasonable window size
|
||||
pixelWindow.Resize(fyne.NewSize(800, 600))
|
||||
|
||||
state := &utils.State{
|
||||
BrushColor: color.NRGBA{255, 255, 255, 255},
|
||||
PaletteSelected: 0,
|
||||
}
|
||||
|
||||
pixlCanvasConfig := utils.PxCanvasConfig{
|
||||
DrawingArea: fyne.NewSize(600, 600),
|
||||
PxCols: 50, // Set to 50x50 grid
|
||||
PxRows: 50, // Set to 50x50 grid
|
||||
PxSize: 10, // Set pixel size for proper visibility
|
||||
CanvasOffset: fyne.NewPos(100, 100), // Initial offset for visibility
|
||||
}
|
||||
|
||||
pixlCanvas := core.NewPxCanvas(state, pixlCanvasConfig)
|
||||
|
||||
appInit := ui.Appinit{
|
||||
Pixlcanvas: pixlCanvas,
|
||||
PixlWindow: pixelWindow,
|
||||
State: state,
|
||||
Palettes: make([]*utils.Palette, 0, 64),
|
||||
}
|
||||
|
||||
ui.Setup(&appInit)
|
||||
pixelWindow.ShowAndRun()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/abdul756/Pixel-Editor/src/utils"
|
||||
)
|
||||
|
||||
func SetupBrushes(app *Appinit) *fyne.Container {
|
||||
// Create a radio group for brush types
|
||||
brushTypeLabel := widget.NewLabel("Tool Type:")
|
||||
|
||||
// Radio buttons for different brush types
|
||||
brushRadio := widget.NewRadioGroup(
|
||||
[]string{"Brush", "Eraser"},
|
||||
func(value string) {
|
||||
switch value {
|
||||
case "Brush":
|
||||
app.State.BrushType = utils.BrushTypePen
|
||||
case "Eraser":
|
||||
app.State.BrushType = utils.BrushTypeEraser
|
||||
}
|
||||
fmt.Println("Tool set to:", value)
|
||||
},
|
||||
)
|
||||
|
||||
// Set default brush type to Brush
|
||||
brushRadio.SetSelected("Brush")
|
||||
|
||||
// Add a label to show the current pixel coordinates
|
||||
positionLabel := widget.NewLabel("Position: (0, 0)")
|
||||
|
||||
// Create a container for brush controls
|
||||
return container.NewVBox(
|
||||
brushTypeLabel,
|
||||
brushRadio,
|
||||
positionLabel,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2/container"
|
||||
)
|
||||
|
||||
func Setup(app *Appinit) {
|
||||
// Create the components
|
||||
paletteContainer := BuildPalettes(app)
|
||||
colorPicker := SetupColorPicker(app)
|
||||
brushes := SetupBrushes(app)
|
||||
|
||||
// Create toolbar on the right
|
||||
rightToolbar := container.NewVBox(
|
||||
colorPicker,
|
||||
brushes,
|
||||
)
|
||||
|
||||
// Create a simple layout with the canvas
|
||||
appLayout := container.NewBorder(
|
||||
nil, // top
|
||||
paletteContainer, // bottom
|
||||
nil, // left
|
||||
rightToolbar, // right - now includes brushes
|
||||
app.Pixlcanvas, // center - directly use the canvas for drawing and dragging
|
||||
)
|
||||
|
||||
// Set the content
|
||||
app.PixlWindow.SetContent(appLayout)
|
||||
app.PixlWindow.CenterOnScreen()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"github.com/abdul756/Pixel-Editor/src/utils"
|
||||
)
|
||||
|
||||
func BuildPalettes(app *Appinit) *fyne.Container {
|
||||
canvasPalattes := make([]fyne.CanvasObject, 0, 64)
|
||||
for i := 0; i < cap(app.Palettes); i++ {
|
||||
initialColor := color.NRGBA{255, 255, 255, 255}
|
||||
p := utils.NewPalette(app.State, initialColor, i, func(p *utils.Palette) {
|
||||
for j := 0; j < len(app.Palettes); j++ {
|
||||
app.Palettes[j].Selected = false
|
||||
canvasPalattes[j].Refresh()
|
||||
}
|
||||
app.State.PaletteSelected = p.PaletteIndex
|
||||
app.State.BrushColor = p.Color
|
||||
|
||||
})
|
||||
if i == 0 {
|
||||
p.Selected = true
|
||||
app.State.PaletteSelected = 0
|
||||
p.Refresh()
|
||||
|
||||
}
|
||||
|
||||
app.Palettes = append(app.Palettes, p)
|
||||
canvasPalattes = append(canvasPalattes, p)
|
||||
}
|
||||
return container.NewGridWrap(fyne.NewSize(20, 20), canvasPalattes...)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"github.com/lusingander/colorpicker"
|
||||
)
|
||||
|
||||
func SetupColorPicker(app *Appinit) *fyne.Container {
|
||||
picker := colorpicker.New(200 /* height */, colorpicker.StyleHue /* Style */)
|
||||
picker.SetOnChanged(func(c color.Color) {
|
||||
app.State.BrushColor = c
|
||||
app.Palettes[app.State.PaletteSelected].SetColor(c)
|
||||
|
||||
})
|
||||
|
||||
return container.NewVBox(picker)
|
||||
}
|
||||
|
||||
// you can use it just like any other Fyne widget
|
||||
@@ -0,0 +1,14 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/abdul756/Pixel-Editor/src/core"
|
||||
"github.com/abdul756/Pixel-Editor/src/utils"
|
||||
)
|
||||
|
||||
type Appinit struct {
|
||||
Pixlcanvas *core.PxCanvas
|
||||
PixlWindow fyne.Window
|
||||
State *utils.State
|
||||
Palettes []*utils.Palette
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// BrushType defines the type of brush being used
|
||||
type BrushType = int
|
||||
|
||||
// Brush Type constants
|
||||
const (
|
||||
BrushTypePen BrushType = iota // Pen brush for drawing individual pixels
|
||||
BrushTypeEraser // Eraser brush
|
||||
)
|
||||
|
||||
// Brushable is an interface for objects that can be painted on
|
||||
type Brushable interface {
|
||||
SetColor(c color.Color, x, y int)
|
||||
PaintSwatch(p fyne.Position)
|
||||
}
|
||||
|
||||
type PxCanvasConfig struct {
|
||||
DrawingArea fyne.Size
|
||||
CanvasOffset fyne.Position
|
||||
PxRows, PxCols int
|
||||
PxSize int
|
||||
}
|
||||
|
||||
// State contains the active UI information
|
||||
type State struct {
|
||||
BrushColor color.Color
|
||||
BrushType int
|
||||
PaletteSelected int // Used in apptype.go
|
||||
SwatchSelected int // Used in state.go
|
||||
FilePath string
|
||||
BrushSize int
|
||||
AreaSize int // Size of the painted area
|
||||
}
|
||||
|
||||
func (state *State) SetFilePath(path string) {
|
||||
state.FilePath = path
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package utils
|
||||
|
||||
import "fyne.io/fyne/v2/driver/desktop"
|
||||
|
||||
func (palette *Palette) MouseDown(ev *desktop.MouseEvent) {
|
||||
palette.clickHandler(palette)
|
||||
palette.Selected = true
|
||||
palette.Refresh()
|
||||
}
|
||||
|
||||
func (palette *Palette) MouseUp(ev *desktop.MouseEvent) {}
|
||||
@@ -0,0 +1,50 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type Palette struct {
|
||||
widget.BaseWidget
|
||||
Selected bool
|
||||
Color color.Color
|
||||
PaletteIndex int
|
||||
clickHandler func(palette *Palette)
|
||||
}
|
||||
|
||||
func (p *Palette) SetColor(c color.Color) {
|
||||
p.Color = c
|
||||
p.Refresh()
|
||||
}
|
||||
|
||||
func NewPalette(state *State, color color.Color, PaletteIndex int, clickHandler func(palette *Palette)) *Palette {
|
||||
palette := &Palette{
|
||||
Selected: false,
|
||||
Color: color,
|
||||
clickHandler: clickHandler,
|
||||
PaletteIndex: PaletteIndex,
|
||||
}
|
||||
palette.ExtendBaseWidget(palette)
|
||||
|
||||
return palette
|
||||
}
|
||||
|
||||
// Tapped handles mouse click event
|
||||
func (palette *Palette) Tapped(_ *fyne.PointEvent) {
|
||||
palette.clickHandler(palette)
|
||||
}
|
||||
|
||||
func (palette *Palette) CreateRenderer() fyne.WidgetRenderer {
|
||||
square := canvas.NewRectangle(palette.Color)
|
||||
objects := []fyne.CanvasObject{square}
|
||||
|
||||
return &PaletteRenderer{
|
||||
square: *square,
|
||||
objects: objects,
|
||||
parent: palette,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
)
|
||||
|
||||
type PaletteRenderer struct {
|
||||
square canvas.Rectangle
|
||||
objects []fyne.CanvasObject
|
||||
parent *Palette
|
||||
}
|
||||
|
||||
func (renderer *PaletteRenderer) MinSize() fyne.Size {
|
||||
return fyne.NewSize(30, 30)
|
||||
}
|
||||
|
||||
func (renderer *PaletteRenderer) Layout(size fyne.Size) {
|
||||
renderer.objects[0].Resize(size)
|
||||
}
|
||||
func (renderer *PaletteRenderer) Refresh() {
|
||||
renderer.Layout(fyne.NewSize(30, 30))
|
||||
renderer.square.FillColor = renderer.parent.Color
|
||||
renderer.square.StrokeWidth = 1
|
||||
renderer.square.StrokeColor = color.NRGBA{0, 0, 0, 255} // Black border for all palettes
|
||||
|
||||
if renderer.parent.Selected {
|
||||
renderer.square.StrokeWidth = 3
|
||||
renderer.square.StrokeColor = color.NRGBA{255, 255, 255, 255} // White for selected
|
||||
}
|
||||
|
||||
renderer.objects[0] = &renderer.square
|
||||
canvas.Refresh(renderer.parent)
|
||||
}
|
||||
|
||||
func (renderer *PaletteRenderer) Objects() []fyne.CanvasObject {
|
||||
return renderer.objects
|
||||
}
|
||||
func (renderer *PaletteRenderer) Destroy() {}
|
||||
Reference in new issue
Block a user