mtg_card_maker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/CODE_OF_CONDUCT.md +131 -0
- data/LICENSE.txt +20 -0
- data/README.md +305 -0
- data/bin/mtg_card_maker +7 -0
- data/lib/mtg_card_maker/base_card.rb +210 -0
- data/lib/mtg_card_maker/cli.rb +221 -0
- data/lib/mtg_card_maker/color_palette.rb +135 -0
- data/lib/mtg_card_maker/color_scheme.rb +305 -0
- data/lib/mtg_card_maker/core_ext/deep_merge.rb +21 -0
- data/lib/mtg_card_maker/fonts/Goudy Mediaeval DemiBold.ttf +0 -0
- data/lib/mtg_card_maker/fonts/goudy_base64.txt +1 -0
- data/lib/mtg_card_maker/icon_service.rb +95 -0
- data/lib/mtg_card_maker/icons/black.svg +1 -0
- data/lib/mtg_card_maker/icons/blue.svg +1 -0
- data/lib/mtg_card_maker/icons/colorless.svg +1 -0
- data/lib/mtg_card_maker/icons/green.svg +1 -0
- data/lib/mtg_card_maker/icons/jsharp.svg +1 -0
- data/lib/mtg_card_maker/icons/qrcode.svg +1 -0
- data/lib/mtg_card_maker/icons/red.svg +1 -0
- data/lib/mtg_card_maker/icons/white.svg +1 -0
- data/lib/mtg_card_maker/layer_config.rb +289 -0
- data/lib/mtg_card_maker/layer_factory.rb +122 -0
- data/lib/mtg_card_maker/layer_initializer.rb +12 -0
- data/lib/mtg_card_maker/layers/art_layer.rb +63 -0
- data/lib/mtg_card_maker/layers/border_layer.rb +166 -0
- data/lib/mtg_card_maker/layers/frame_layer.rb +62 -0
- data/lib/mtg_card_maker/layers/name_layer.rb +82 -0
- data/lib/mtg_card_maker/layers/power_layer.rb +69 -0
- data/lib/mtg_card_maker/layers/text_box_layer.rb +107 -0
- data/lib/mtg_card_maker/layers/type_line_layer.rb +86 -0
- data/lib/mtg_card_maker/mana_cost.rb +220 -0
- data/lib/mtg_card_maker/metallic_renderer.rb +174 -0
- data/lib/mtg_card_maker/sprite_sheet_assets.rb +158 -0
- data/lib/mtg_card_maker/sprite_sheet_builder.rb +90 -0
- data/lib/mtg_card_maker/sprite_sheet_service.rb +126 -0
- data/lib/mtg_card_maker/svg_gradient_service.rb +159 -0
- data/lib/mtg_card_maker/text_rendering_service.rb +160 -0
- data/lib/mtg_card_maker/version.rb +8 -0
- data/lib/mtg_card_maker.rb +268 -0
- data/sig/mtg_card_maker.rbs +4 -0
- metadata +149 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'layers/art_layer'
|
4
|
+
require_relative 'layers/text_box_layer'
|
5
|
+
require_relative 'layers/frame_layer'
|
6
|
+
require_relative 'layers/name_layer'
|
7
|
+
require_relative 'layers/border_layer'
|
8
|
+
require_relative 'layers/power_layer'
|
9
|
+
require_relative 'layers/type_line_layer'
|
10
|
+
|
11
|
+
module MtgCardMaker
|
12
|
+
# Base class for all Magic: The Gathering card types that provides common
|
13
|
+
# functionality with simplified configuration. This class handles the creation
|
14
|
+
# of complete MTG cards with all necessary layers (frames, text, art, etc.)
|
15
|
+
# using predefined dimensions and layouts.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# card = MtgCardMaker::BaseCard.new(
|
19
|
+
# name: "Lightning Bolt",
|
20
|
+
# mana_cost: "R",
|
21
|
+
# type_line: "Instant",
|
22
|
+
# rules_text: "Lightning Bolt deals 3 damage to any target.",
|
23
|
+
# color_scheme: :red
|
24
|
+
# )
|
25
|
+
# card.save("lightning_bolt.svg")
|
26
|
+
#
|
27
|
+
# @since 0.1.0
|
28
|
+
class BaseCard
|
29
|
+
# Fixed defaults for card layout and dimensions
|
30
|
+
# @return [Hash] the default configuration for card layers and dimensions
|
31
|
+
DEFAULTS = {
|
32
|
+
layers: {
|
33
|
+
border: {
|
34
|
+
x: 0,
|
35
|
+
y: 0,
|
36
|
+
width: 630,
|
37
|
+
height: 880
|
38
|
+
},
|
39
|
+
frame: {
|
40
|
+
x: 10,
|
41
|
+
y: 10,
|
42
|
+
width: 610,
|
43
|
+
height: 860
|
44
|
+
},
|
45
|
+
name_area: {
|
46
|
+
x: 30,
|
47
|
+
y: 40,
|
48
|
+
width: 570,
|
49
|
+
height: 50
|
50
|
+
},
|
51
|
+
art_layer: {
|
52
|
+
x: 40,
|
53
|
+
y: 95,
|
54
|
+
width: 550,
|
55
|
+
height: 400,
|
56
|
+
corner_radius: { x: 5, y: 5 }
|
57
|
+
},
|
58
|
+
type_area: {
|
59
|
+
x: 30,
|
60
|
+
y: 500,
|
61
|
+
width: 570,
|
62
|
+
height: 40
|
63
|
+
},
|
64
|
+
text_box: {
|
65
|
+
x: 40,
|
66
|
+
y: 545,
|
67
|
+
width: 550,
|
68
|
+
height: 265
|
69
|
+
},
|
70
|
+
power_area: {
|
71
|
+
x: 455,
|
72
|
+
y: 790,
|
73
|
+
width: 140,
|
74
|
+
height: 40
|
75
|
+
}
|
76
|
+
},
|
77
|
+
mask_id: 'artWindowMask',
|
78
|
+
frame_stroke_width: 2
|
79
|
+
}.freeze
|
80
|
+
|
81
|
+
# @return [String, nil] the card name
|
82
|
+
attr_reader :name
|
83
|
+
|
84
|
+
# @return [String, nil] the mana cost in MTG notation (e.g., "R", "1U")
|
85
|
+
attr_reader :mana_cost
|
86
|
+
|
87
|
+
# @return [String, nil] the card type (e.g., "Instant", "Creature")
|
88
|
+
attr_reader :type_line
|
89
|
+
|
90
|
+
# @return [String, nil] the card description/rules text
|
91
|
+
attr_reader :rules_text
|
92
|
+
|
93
|
+
# @return [String, nil] the flavor text (italic text at bottom)
|
94
|
+
attr_reader :flavor_text
|
95
|
+
|
96
|
+
# @return [String, nil] the power value for creatures
|
97
|
+
attr_reader :power
|
98
|
+
|
99
|
+
# @return [String, nil] the toughness value for creatures
|
100
|
+
attr_reader :toughness
|
101
|
+
|
102
|
+
# @return [String, nil] the border color
|
103
|
+
attr_reader :border_color
|
104
|
+
|
105
|
+
# @return [ColorScheme] the color scheme for the card
|
106
|
+
attr_reader :color_scheme
|
107
|
+
|
108
|
+
# @return [String, nil] the URL or path for the card artwork
|
109
|
+
attr_reader :art
|
110
|
+
|
111
|
+
# Initialize a new card with the given configuration
|
112
|
+
#
|
113
|
+
# @param config [Hash] the card configuration
|
114
|
+
# @option config [String] :name the card name
|
115
|
+
# @option config [String] :mana_cost the mana cost in MTG notation
|
116
|
+
# @option config [String] :type_line the card type
|
117
|
+
# @option config [String] :rules_text the card rules text
|
118
|
+
# @option config [String] :flavor_text the flavor text
|
119
|
+
# @option config [String] :power the power value for creatures
|
120
|
+
# @option config [String] :toughness the toughness value for creatures
|
121
|
+
# @option config [String] :border_color the border color
|
122
|
+
# @option config [Symbol, String] :color the color scheme
|
123
|
+
# @option config [String] :art the URL or path for card artwork
|
124
|
+
def initialize(config)
|
125
|
+
assign_attributes(config)
|
126
|
+
@template = Template.new
|
127
|
+
add_layers
|
128
|
+
end
|
129
|
+
|
130
|
+
# Save the card to an SVG file
|
131
|
+
#
|
132
|
+
# @param filename [String] the filename to save to
|
133
|
+
# @return [void]
|
134
|
+
def save(filename)
|
135
|
+
@template.save(filename)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @private
|
139
|
+
def use_color_scheme(color)
|
140
|
+
if color
|
141
|
+
ColorScheme.new(color)
|
142
|
+
else
|
143
|
+
DEFAULT_COLOR_SCHEME
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Delegate dimension methods to DEFAULTS for LayerFactory compatibility
|
148
|
+
# @private
|
149
|
+
def card_width
|
150
|
+
CARD_WIDTH
|
151
|
+
end
|
152
|
+
|
153
|
+
# @private
|
154
|
+
def card_height
|
155
|
+
CARD_HEIGHT
|
156
|
+
end
|
157
|
+
|
158
|
+
# @private
|
159
|
+
def dimensions_for_layer(layer_name)
|
160
|
+
DEFAULTS[:layers][layer_name.to_sym] || {}
|
161
|
+
end
|
162
|
+
|
163
|
+
# @private
|
164
|
+
def art_window_config
|
165
|
+
DEFAULTS[:layers][:art_layer]
|
166
|
+
end
|
167
|
+
|
168
|
+
# @private
|
169
|
+
def frame_stroke_width
|
170
|
+
DEFAULTS[:frame_stroke_width]
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def assign_attributes(config)
|
176
|
+
@name = config[:name]
|
177
|
+
@mana_cost = config[:mana_cost]
|
178
|
+
@type_line = config[:type_line]
|
179
|
+
@rules_text = config[:rules_text]
|
180
|
+
@flavor_text = config[:flavor_text]
|
181
|
+
@power = config[:power]
|
182
|
+
@toughness = config[:toughness]
|
183
|
+
@border_color = config[:border_color]
|
184
|
+
@color_scheme = use_color_scheme(config[:color])
|
185
|
+
@art = config[:art]
|
186
|
+
end
|
187
|
+
|
188
|
+
def define_art_window_mask
|
189
|
+
svg = @template.instance_variable_get(:@svg)
|
190
|
+
svg.defs do
|
191
|
+
svg.mask id: DEFAULTS[:mask_id] do
|
192
|
+
# White rectangle covers the entire card (opaque)
|
193
|
+
svg.rect x: 0, y: 0, width: '100%', height: '100%', fill: '#FFF'
|
194
|
+
# Black rectangle creates the transparent window at art position
|
195
|
+
art_config = art_window_config
|
196
|
+
svg.rect x: art_config[:x], y: art_config[:y],
|
197
|
+
width: art_config[:width], height: art_config[:height],
|
198
|
+
fill: '#000', rx: art_config[:corner_radius][:x], ry: art_config[:corner_radius][:y]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def add_layers
|
204
|
+
define_art_window_mask
|
205
|
+
# Use LayerFactory to create layers in order
|
206
|
+
layers = LayerFactory.create_layers_for_card(self, DEFAULTS[:mask_id], self)
|
207
|
+
layers.each { |layer| @template.add_layer(layer) }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'yaml'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module MtgCardMaker
|
8
|
+
# Thor-based command-line interface for MTG Card Maker.
|
9
|
+
# Provides commands for generating individual cards and sprite sheets
|
10
|
+
# from YAML configuration files.
|
11
|
+
#
|
12
|
+
# @example Generate a single card
|
13
|
+
# mtg_card_maker generate_card --name "Lightning Bolt" --type-text "Instant" \
|
14
|
+
# --rules-text "Lightning Bolt deals 3 damage to any target." --color red
|
15
|
+
#
|
16
|
+
# @example Generate a sprite sheet
|
17
|
+
# mtg_card_maker generate_sprite cards.yml sprite.svg
|
18
|
+
#
|
19
|
+
# @example Add a card to YAML file
|
20
|
+
# mtg_card_maker add_card cards.yml --name "Lightning Bolt" --type-text "Instant" \
|
21
|
+
# --rules-text "Lightning Bolt deals 3 damage to any target." --color red
|
22
|
+
#
|
23
|
+
# @since 0.1.0
|
24
|
+
class CLI < Thor # rubocop:disable Metrics/ClassLength
|
25
|
+
package_name 'mtg_card_maker'
|
26
|
+
map 'a' => :add_card
|
27
|
+
map 'ac' => :add_card
|
28
|
+
map 'g' => :generate_card
|
29
|
+
map 'gc' => :generate_card
|
30
|
+
map 'gcs'=> :generate_sprite
|
31
|
+
map 'gs' => :generate_sprite
|
32
|
+
|
33
|
+
def self.exit_on_failure?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define the standard card options for CLI commands
|
38
|
+
#
|
39
|
+
# @return [Hash] the card option definitions for Thor
|
40
|
+
def self.card_options
|
41
|
+
{
|
42
|
+
name: { type: :string, required: true, desc: 'Card name' },
|
43
|
+
mana_cost: { type: :string, aliases: ['mana', 'cost'], desc: 'Mana cost (e.g., "2RR", "XG")' },
|
44
|
+
type_line: { type: :string, required: true, aliases: ['type'],
|
45
|
+
desc: 'Card type & subtype (e.g., "Creature - Dragon", "Instant")' },
|
46
|
+
rules_text: { type: :string, required: true, aliases: ['rules'], desc: 'Card rules text' },
|
47
|
+
flavor_text: { type: :string, aliases: ['flavor'], desc: 'Flavor text (optional)' },
|
48
|
+
power: { type: :string, desc: 'Power (for creatures)' },
|
49
|
+
toughness: { type: :string, desc: 'Toughness (for creatures)' },
|
50
|
+
border_color: { type: :string, aliases: ['border'], desc: 'Border color (white, black, gold, silver)' },
|
51
|
+
color: { type: :string,
|
52
|
+
default: 'colorless',
|
53
|
+
desc: 'Card color (white, blue, black, red, green, colorless)' },
|
54
|
+
art: { type: :string, aliases: ['artwork', 'image'], desc: 'Image URL or path for card artwork' }
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Generate a single MTG card with specified parameters
|
59
|
+
#
|
60
|
+
# @option options [String] :name the card name (required)
|
61
|
+
# @option options [String] :mana_cost the mana cost in MTG notation
|
62
|
+
# @option options [String] :type_line the card type (required)
|
63
|
+
# @option options [String] :rules_text the card rules (required)
|
64
|
+
# @option options [String] :flavor_text the flavor text
|
65
|
+
# @option options [String] :power the power value for creatures
|
66
|
+
# @option options [String] :toughness the toughness value for creatures
|
67
|
+
# @option options [String] :border_color the border color
|
68
|
+
# @option options [String] :color the card color scheme
|
69
|
+
# @option options [String] :art the URL for card artwork
|
70
|
+
# @option options [String] :output the output filename (default: output_card.svg)
|
71
|
+
# @return [void]
|
72
|
+
# @!method generate_card
|
73
|
+
# Generate a single MTG card with specified parameters
|
74
|
+
desc 'generate_card [OPTIONS]', 'Generate a single MTG card with specified parameters'
|
75
|
+
card_options.each { |option, config| option option, config }
|
76
|
+
option :output, type: :string, default: 'output_card.svg', desc: 'Output filename'
|
77
|
+
def generate_card
|
78
|
+
config = build_card_config_from_options
|
79
|
+
# Convert string keys to symbols for BaseCard compatibility
|
80
|
+
config = config.transform_keys(&:to_sym)
|
81
|
+
card = BaseCard.new(config)
|
82
|
+
card.save(options[:output])
|
83
|
+
puts "✨ Generated #{options[:output]}! ✨"
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!method generate_sprite(yaml_file, output_file)
|
87
|
+
# Generate a sprite sheet from YAML configuration
|
88
|
+
desc 'generate_sprite YAML_FILE OUTPUT_FILE [OPTIONS]', 'Generate a sprite sheet from YAML configuration'
|
89
|
+
option :cards_per_row, type: :numeric, default: 4, desc: 'Number of cards per row in sprite'
|
90
|
+
option :spacing, type: :numeric, default: 30, desc: 'Spacing between cards in pixels'
|
91
|
+
def generate_sprite(yaml_file, output_file)
|
92
|
+
validate_yaml_file(yaml_file)
|
93
|
+
|
94
|
+
begin
|
95
|
+
config = load_yaml_config(yaml_file)
|
96
|
+
sprite_service = create_sprite_service
|
97
|
+
process_sprite_generation(config, sprite_service, output_file)
|
98
|
+
rescue Psych::SyntaxError => e
|
99
|
+
handle_yaml_error(e)
|
100
|
+
rescue StandardError => e
|
101
|
+
handle_general_error(e)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# @!method add_card(yaml_file)
|
106
|
+
# Add a new card configuration to YAML file
|
107
|
+
desc 'add_card YAML_FILE [OPTIONS]', 'Add a new card configuration to YAML file'
|
108
|
+
card_options.each { |option, config| option option, config }
|
109
|
+
def add_card(yaml_file)
|
110
|
+
# Create directory if it doesn't exist
|
111
|
+
FileUtils.mkdir_p(File.dirname(yaml_file))
|
112
|
+
|
113
|
+
# Load existing config or create new one
|
114
|
+
config = File.exist?(yaml_file) ? YAML.safe_load_file(yaml_file) : {}
|
115
|
+
|
116
|
+
# Generate a unique key for the card
|
117
|
+
card_key = generate_card_key(options[:name], config)
|
118
|
+
|
119
|
+
# Build card configuration
|
120
|
+
card_config = build_card_config_from_options
|
121
|
+
|
122
|
+
# Add to config
|
123
|
+
config[card_key] = card_config
|
124
|
+
|
125
|
+
# Save back to file
|
126
|
+
File.write(yaml_file, config.to_yaml)
|
127
|
+
puts "✨ Added card '#{options[:name]}' to #{yaml_file}! ✨"
|
128
|
+
puts "🎴 Key: #{card_key}"
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def validate_yaml_file(yaml_file)
|
134
|
+
return if File.exist?(yaml_file)
|
135
|
+
|
136
|
+
warn "❌ YAML file not found: #{yaml_file}"
|
137
|
+
exit 1
|
138
|
+
end
|
139
|
+
|
140
|
+
def process_sprite_generation(config, sprite_service, output_file)
|
141
|
+
if sprite_service.create_sprite_sheet(config, output_file)
|
142
|
+
display_success_message(config, sprite_service, output_file)
|
143
|
+
else
|
144
|
+
warn '❌ Failed to generate sprite sheet'
|
145
|
+
exit 1
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def handle_yaml_error(error)
|
150
|
+
warn "❌ Invalid YAML syntax: #{error.message}"
|
151
|
+
exit 1
|
152
|
+
end
|
153
|
+
|
154
|
+
def handle_general_error(error)
|
155
|
+
warn "💥 Error: #{error.message}"
|
156
|
+
exit 1
|
157
|
+
end
|
158
|
+
|
159
|
+
def display_success_message(config, sprite_service, output_file)
|
160
|
+
puts "✨ Generated #{output_file}! ✨"
|
161
|
+
width, height = sprite_service.sprite_dimensions(config.length)
|
162
|
+
puts "📏 Sprite dimensions: #{width}x#{height} pixels"
|
163
|
+
puts "🎨 Contains #{config.length} cards"
|
164
|
+
end
|
165
|
+
|
166
|
+
def load_yaml_config(yaml_file)
|
167
|
+
YAML.safe_load_file(yaml_file)
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_sprite_service
|
171
|
+
SpriteSheetService.new(
|
172
|
+
cards_per_row: options[:cards_per_row],
|
173
|
+
spacing: options[:spacing]
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
def build_card_config_from_options
|
178
|
+
config = build_required_config
|
179
|
+
add_optional_fields(config)
|
180
|
+
config
|
181
|
+
end
|
182
|
+
|
183
|
+
def build_required_config
|
184
|
+
{
|
185
|
+
'name' => options[:name],
|
186
|
+
'type_line' => options[:type_line],
|
187
|
+
'rules_text' => process_newlines(options[:rules_text])
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
def add_optional_fields(config)
|
192
|
+
optional_fields = %w[mana_cost power toughness border_color color art]
|
193
|
+
optional_fields.each do |field|
|
194
|
+
config[field] = options[field.to_sym] if options[field.to_sym]
|
195
|
+
end
|
196
|
+
|
197
|
+
# Handle flavor_text separately to process newlines
|
198
|
+
return unless options[:flavor_text]
|
199
|
+
|
200
|
+
config['flavor_text'] = process_newlines(options[:flavor_text])
|
201
|
+
end
|
202
|
+
|
203
|
+
def process_newlines(text)
|
204
|
+
# Convert literal \n to actual newlines
|
205
|
+
text.gsub('\\n', "\n")
|
206
|
+
end
|
207
|
+
|
208
|
+
def generate_card_key(name, existing_config)
|
209
|
+
base_key = name.downcase.gsub(/[^a-z0-9]/, '_').squeeze('_').chomp('_')
|
210
|
+
|
211
|
+
# If key already exists, append a number
|
212
|
+
if existing_config.key?(base_key)
|
213
|
+
counter = 1
|
214
|
+
counter += 1 while existing_config.key?("#{base_key}_#{counter}")
|
215
|
+
"#{base_key}_#{counter}"
|
216
|
+
else
|
217
|
+
base_key
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MtgCardMaker
|
4
|
+
# ColorPalette aggregates the five core interface colors for cohesive design.
|
5
|
+
# It focuses on background elements, buttons, borders, and other UI components,
|
6
|
+
# excluding text color. Multiple instances can be created for different themes
|
7
|
+
# (default, dark, corporate, etc.).
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# palette = MtgCardMaker::ColorPalette.new(
|
11
|
+
# primary_color: '#42A5F5',
|
12
|
+
# background_color: '#E3F2FD',
|
13
|
+
# border_color: '#1565C0'
|
14
|
+
# )
|
15
|
+
# palette.primary_color # => "#42A5F5"
|
16
|
+
#
|
17
|
+
# @example Using predefined themes
|
18
|
+
# dark_palette = MtgCardMaker::ColorPalette.dark
|
19
|
+
# light_palette = MtgCardMaker::ColorPalette.light
|
20
|
+
# default_palette = MtgCardMaker::ColorPalette.default
|
21
|
+
#
|
22
|
+
# @since 0.1.0
|
23
|
+
class ColorPalette
|
24
|
+
# @return [String] the primary color
|
25
|
+
attr_reader :primary_color
|
26
|
+
|
27
|
+
# @return [String] the background color
|
28
|
+
attr_reader :background_color
|
29
|
+
|
30
|
+
# @return [String] the border color
|
31
|
+
attr_reader :border_color
|
32
|
+
|
33
|
+
# @return [String] the frame stroke color
|
34
|
+
attr_reader :frame_stroke_color
|
35
|
+
|
36
|
+
# @return [String] the accent color
|
37
|
+
attr_reader :accent_color
|
38
|
+
|
39
|
+
# Default frame stroke color used across the application
|
40
|
+
# @return [String] the default frame stroke color
|
41
|
+
FRAME_STROKE_COLOR = '#111'
|
42
|
+
|
43
|
+
# Initialize a new color palette
|
44
|
+
#
|
45
|
+
# @param primary_color [String, nil] the primary color (default: from default color scheme)
|
46
|
+
# @param background_color [String, nil] the background color (default: from default color scheme)
|
47
|
+
# @param border_color [String, nil] the border color (default: from default color scheme)
|
48
|
+
# @param frame_stroke_color [String] the frame stroke color (default: FRAME_STROKE_COLOR)
|
49
|
+
# @param accent_color [String, nil] the accent color (default: from default color scheme)
|
50
|
+
def initialize(primary_color: nil, background_color: nil, border_color: nil,
|
51
|
+
frame_stroke_color: FRAME_STROKE_COLOR, accent_color: nil)
|
52
|
+
@primary_color = primary_color || DEFAULT_COLOR_SCHEME.primary_color
|
53
|
+
@background_color = background_color || DEFAULT_COLOR_SCHEME.background_color
|
54
|
+
@border_color = border_color || DEFAULT_COLOR_SCHEME.border_color
|
55
|
+
@frame_stroke_color = frame_stroke_color
|
56
|
+
@accent_color = accent_color || DEFAULT_COLOR_SCHEME.primary_color
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create a palette from a color scheme
|
60
|
+
#
|
61
|
+
# @param color_scheme [ColorScheme] the color scheme to create palette from
|
62
|
+
# @return [ColorPalette] a new color palette based on the color scheme
|
63
|
+
def self.from_color_scheme(color_scheme)
|
64
|
+
new(
|
65
|
+
primary_color: color_scheme.primary_color,
|
66
|
+
background_color: color_scheme.background_color,
|
67
|
+
border_color: color_scheme.border_color,
|
68
|
+
accent_color: color_scheme.primary_color
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Default palette using the default color scheme
|
73
|
+
#
|
74
|
+
# @return [ColorPalette] the default color palette
|
75
|
+
def self.default
|
76
|
+
from_color_scheme(DEFAULT_COLOR_SCHEME)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Dark theme palette
|
80
|
+
#
|
81
|
+
# @return [ColorPalette] a dark theme color palette
|
82
|
+
def self.dark
|
83
|
+
new(
|
84
|
+
primary_color: '#2A2A2A',
|
85
|
+
background_color: '#1A1A1A',
|
86
|
+
border_color: '#4A4A4A',
|
87
|
+
frame_stroke_color: '#333',
|
88
|
+
accent_color: '#6B6B6B'
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Light theme palette
|
93
|
+
#
|
94
|
+
# @return [ColorPalette] a light theme color palette
|
95
|
+
def self.light
|
96
|
+
new(
|
97
|
+
primary_color: '#E8E8E8',
|
98
|
+
background_color: '#F5F5F5',
|
99
|
+
border_color: '#D4D4D4',
|
100
|
+
frame_stroke_color: '#666',
|
101
|
+
accent_color: '#8B8B8B'
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return all colors as a hash for easy access
|
106
|
+
def to_h
|
107
|
+
{
|
108
|
+
primary_color: @primary_color,
|
109
|
+
background_color: @background_color,
|
110
|
+
border_color: @border_color,
|
111
|
+
frame_stroke_color: @frame_stroke_color,
|
112
|
+
accent_color: @accent_color
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return all colors as an array
|
117
|
+
def to_a
|
118
|
+
[@primary_color, @background_color, @border_color, @frame_stroke_color, @accent_color]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Check if this palette matches another
|
122
|
+
def ==(other)
|
123
|
+
return false unless other.is_a?(ColorPalette)
|
124
|
+
|
125
|
+
to_h == other.to_h
|
126
|
+
end
|
127
|
+
|
128
|
+
alias_method :eql?, :==
|
129
|
+
|
130
|
+
# Generate a hash for this palette
|
131
|
+
def hash
|
132
|
+
to_h.hash
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|