Initial project setup with Docker support, Go modules, and a basic pixel art editor.

This commit is contained in:
abdul.manaf1 committed 2025-05-01 07:19:53 +05:30
1 parent ced23f8091
commit 33add40f66
24 files changed
+1320 -1

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
.git
.idea
.vscode
*.md
!README.md
+35
View File
@@ -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
View File
@@ -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"]
+66 -1
View File
@@ -1 +1,66 @@
# Pixel-Editor
# PixelEditor
[![Fyne](https://img.shields.io/badge/Fyne-2.6.0-blue)](https://fyne.io/)
[![License](https://img.shields.io/badge/license-MIT-green)](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
+17
View File
@@ -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
+45
View File
@@ -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
)
+82
View File
@@ -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
View File
@@ -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
Executable
+63
View File
@@ -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
+76
View File
@@ -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
}
+120
View File
@@ -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
}
+105
View File
@@ -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
}
+125
View File
@@ -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()
}
}
+182
View File
@@ -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
}
+44
View File
@@ -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()
}
+42
View File
@@ -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,
)
}
+31
View File
@@ -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()
}
+35
View File
@@ -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...)
}
+22
View File
@@ -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
+14
View File
@@ -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
}
+44
View File
@@ -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
}
+11
View File
@@ -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) {}
+50
View File
@@ -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,
}
}
+41
View File
@@ -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() {}