DEV Community

Andy Maleh
Andy Maleh

Posted on • Edited on

5 1

CryptoPunks GUI by Glimmer DSL for Tk

CryptoPunks GUI is a Graphical User Interface for the famous cryptopunks Ruby gem, built with Glimmer DSL for Tk.

Screenshot

Screenshot 2

Screenshot 3

Screenshot 4

Screenshot 5

Screenshot 6

Screenshot 7

Screenshot 8

Screenshot 9

Screenshot 10

Screenshot 11

# From: https://github.com/AndyObtiva/cryptopunks-gui

require 'glimmer-dsl-tk'
require 'cryptopunks'
require 'facets'
require 'fileutils'
require 'net/http'
require 'uri'
require 'glimmer/data_binding/observer'
require 'yaml'
require 'puts_debuggerer'

class CryptopunksGui
  include Glimmer

  PALETTES = ['Standard'] + (Palette8bit.constants).map(&:name).map {|palette| palette.split('_').map(&:capitalize).join(' ')}.reject { |palette| palette.include?(' ') }.sort
  STYLES = ['Normal', 'Led', 'Sketch']
  COLLECTION_URL_MAP = {
    'Punks' => 'https://raw.githubusercontent.com/larvalabs/cryptopunks/master/punks.png',
    'Mohawks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/mohawks.png',
    'Blondies' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/blondies.png',
    'Zombies' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/zombies.png',
    'Apes' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/apes.png',
    'Aliens' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/aliens.png',
    'Golden Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/goldenpunks.png',
    'Halloween Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/halloweenpunks.png',
    'Front Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/frontpunks.png',
    'More Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/morepunks.png',
    'Expansion Punks' => 'https://expansionpunks.com/provenance/expansionpunks.png',
    'Avalanche Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/avalanchepunks.png',
    'Intl Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/intlpunks.png',
    'Ape Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/apepunks.png',
    'Alien Clan' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/alienclan.png',
    'Bored Apes Blue' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_blue.png',
    'Bored Apes Red' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_red.png',
    'Bored Apes Neon Glow' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_neon_glow.png',
    'Bored Apes Stars and Stripes' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_stars_and_stripes.png',
    'Bored Apes Acid' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_acid.png',
    'Cool Cats' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats.png',
    'Cool Cats Mohawks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_mohawks.png',
    'Cool Cats Ninjas' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_ninjas.png',
    'Cool Cats TV Heads' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_tvheads.png',
    'Cool Cats Pirates' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_pirates.png',
    'Cool Cats Unicorns' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_unicorns.png',
    'Cool Cats Dragons' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_dragons.png',
    'Cool Cats Frogs' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_frogs.png',
    'Pudgy Penguins' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/pudgypenguins.png',
    'Dodge' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/dodge.png',
    'Rocks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/rocks.png',
    'Punk Rocks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/punkrocks.png',
    'Tulips' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/tulips.png',
  }

  attr_accessor :collection, :image_index, :zoom, :palette, :style, :led_spacing, :led_round_corner, :sketch_line, :flip, :mirror

  def initialize
    initialize_punk_directory
    initialize_collection
    load_config
    initialize_defaults
    observe_image_attribute_changes
    create_gui
    self.image_index = 0
    @root.open
  end

  def collection_options
    COLLECTION_URL_MAP.keys
  end

  def palette_options
    PALETTES
  end

  def style_options
    STYLES
  end

  def initialize_punk_directory
    @punk_directory = @punk_config_directory = File.join(Dir.home, 'cryptopunks')
    FileUtils.mkdir_p(@punk_directory)
  end

  def initialize_collection
    return if @collection && @collection == @last_collection
    @collection ||= COLLECTION_URL_MAP.keys.first
    url = COLLECTION_URL_MAP[@collection]
    @punk_file = File.join(@punk_config_directory, File.basename(url, '.png'))
    File.write(@punk_file, Net::HTTP.get(URI(url))) unless File.exist?(@punk_file)
    @images ||= {}
    @images[@collection] ||= Punks::Image::Composite.read(@punk_file)
    @last_collection = @collection
    self.image_index = 0
    @image_index_spinbox.to = @images[@collection].size - 1 if @image_index_spinbox
  end

  def load_config
    @punk_config_file = File.join(@punk_config_directory, 'cryptopunks.yml')
    FileUtils.touch(@punk_config_file)
    @punk_config = YAML.load(File.read(@punk_config_file)) || {punk_directory: @punk_directory}
    @punk_directory = @punk_config[:punk_directory]
  end

  def save_config
    File.write(@punk_config_file, YAML.dump(@punk_config))
  end

  def initialize_defaults
    @collection = COLLECTION_URL_MAP.keys.first
    @zoom = 12
    @palette = PALETTES.first
    @style = STYLES.first
    @led_spacing = 2
    @led_round_corner = false
    @sketch_line = 1
    @mirror = false
    @flip = false
  end

  def observe_image_attribute_changes
    observer = Glimmer::DataBinding::Observer.proc { generate_image }
    observer.observe(self, :collection)
    observer.observe(self, :image_index)
    observer.observe(self, :zoom)
    observer.observe(self, :palette)
    observer.observe(self, :style)
    observer.observe(self, :led_spacing)
    observer.observe(self, :led_round_corner)
    observer.observe(self, :sketch_line)
    observer.observe(self, :mirror)
    observer.observe(self, :flip)
  end

  def generate_image
    initialize_collection
    return if @image_index.to_i > @images[@collection].size
    image_location = File.join(@punk_directory, "#{@collection.gsub(' ', '').downcase}-#{@image_index}#{"x#{@zoom}" if @zoom.to_i > 1}#{"-#{@palette.underscore}" if @palette != PALETTES.first}#{"-#{@style.underscore}" if @style != STYLES.first}.png")
    puts "Writing punk image to #{image_location}"
    selected_punk = @images[@collection][@image_index.to_i]
    selected_punk = selected_punk.change_palette8bit(Palette8bit.const_get(@palette.gsub(' ', '_').upcase.to_sym)) if @palette != PALETTES.first
    @original_zoom = @zoom
    if @style != STYLES.first
      style_options = {}
      if @style == 'Led'
        style_options[:spacing] = @led_spacing.to_i
        style_options[:round_corner] = @led_round_corner
      end
      if @style == 'Sketch'
        style_options[:line] = @sketch_line.to_i
      end
      selected_punk = selected_punk.send(@style.underscore, @zoom.to_i, **style_options)
    end
    selected_punk = selected_punk.mirror if @mirror
    selected_punk = selected_punk.flip if @flip
    selected_punk = selected_punk.zoom(@zoom.to_i) if @style == STYLES.first
    selected_punk.save(image_location)
    @image_label.image = image_location
    @output_location_entry.text = image_location
    @previous_style = @style
    notify_observers(:zoom) if @zoom != @original_zoom
  end

  def create_gui
    @root = root {
      title 'CryptoPunks GUI'
      iconphoto File.expand_path('../icons/cryptopunks-gui.png', __dir__)

      frame {
        label {
          text 'Collection:'
        }
        combobox {
          readonly true
          text <=> [self, :collection]
        }

        label {
          text 'Image Index:'
        }
        @image_index_spinbox = spinbox {
          from 0
          to @images[@collection].size - 1
          text <=> [self, :image_index]
        }

        label {
          text 'Zoom:'
        }
        spinbox {
          from 1
          to 72
          text <=> [self, :zoom]
        }

        label {
          text 'Palette:'
        }
        combobox {
          readonly true
          text <=> [self, :palette]
        }

        label {
          text 'Style:'
        }
        combobox {
          readonly true
          text <=> [self, :style, after_write: ->(val) {add_style_options}]
        }

        @style_options_frame = frame {
          padding 0
        }

        frame {
          padding 0

          checkbutton {
            grid row: 0, column: 0, column_weight: 0
            variable <=> [self, :mirror]
          }
          label {
            grid row: 0, column: 1
            text 'Mirror'

            on('Button-1') do
              self.mirror = !mirror
            end
          }

          checkbutton {
            grid row: 0, column: 2
            variable <=> [self, :flip]
          }
          label {
            grid row: 0, column: 3
            text 'Flip'

            on('Button-1') do
              self.flip = !flip
            end
          }
        }

        label {
          text 'Output Location:'
        }
        frame {
          padding 0

          @output_location_entry = entry {
            grid row: 0, column: 0
            readonly true
            width 47
          }
          button {
            grid row: 0, column: 1
            text '...'
            width 1.1

            on('command') do
              @punk_directory = choose_directory(parent: @root)
              @punk_config[:punk_directory] = @punk_directory
              save_config
              generate_image
            end
          }
        }

        @image_label = label {
          grid row_weight: 1
        }
      }
    }
  end

  def add_style_options
    @style_options_frame.content {
      @style_options_frame.children.each(&:destroy)
      if @style == 'Led'
        frame {
          padding 0

          label {
            grid row: 0, column: 0, column_weight: 0
            text 'Spacing:'
          }
          spinbox {
            grid row: 0, column: 1
            from 1
            to 72
            text <=> [self, :led_spacing]
          }

          checkbutton {
            grid row: 0, column: 2
            variable <=> [self, :led_round_corner]
          }
          label {
            grid row: 0, column: 3
            text 'Round Corner'

            on('Button-1') do
              self.led_round_corner = !led_round_corner
            end
          }
        }
      elsif @style == 'Sketch'
        frame {
          padding 0

          label {
            grid row: 0, column: 0, column_weight: 0
            text 'Line:'
          }
          spinbox {
            grid row: 0, column: 1
            from 1
            to 72
            text <=> [self, :sketch_line]
          }
        }
      else
        frame { # filler
          padding 0
          height 0
          width 0
        }
      end
    }
  end
end

CryptopunksGui.new.launch
Enter fullscreen mode Exit fullscreen mode

Happy Glimmering!

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay