mirror of
https://github.com/cyberarm/cyberarm_engine.git
synced 2025-12-15 20:52:35 +00:00
Merged in gosu_notifications
This commit is contained in:
83
lib/cyberarm_engine/notification.rb
Normal file
83
lib/cyberarm_engine/notification.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
module CyberarmEngine
|
||||
class Notification
|
||||
WIDTH = 500
|
||||
HEIGHT = 64
|
||||
EDGE_WIDTH = 8
|
||||
TRANSITION_DURATION = 750
|
||||
PADDING = 8
|
||||
|
||||
TTL_LONG = 5_000
|
||||
TTL_MEDIUM = 3_250
|
||||
TTL_SHORT = 1_500
|
||||
TIME_TO_LIVE = TTL_MEDIUM
|
||||
|
||||
BACKGROUND_COLOR = Gosu::Color.new(0xaa313533)
|
||||
EDGE_COLOR = Gosu::Color.new(0xaa010101)
|
||||
ICON_COLOR = Gosu::Color.new(0xddffffff)
|
||||
TITLE_COLOR = Gosu::Color.new(0xddffffff)
|
||||
TAGLINE_COLOR = Gosu::Color.new(0xddaaaaaa)
|
||||
|
||||
TITLE_SIZE = 28
|
||||
TAGLINE_SIZE = 18
|
||||
ICON_SIZE = HEIGHT - PADDING * 2
|
||||
|
||||
TITLE_FONT = Gosu::Font.new(TITLE_SIZE, bold: true)
|
||||
TAGLINE_FONT = Gosu::Font.new(TAGLINE_SIZE)
|
||||
|
||||
PRIORITY_HIGH = 1.0
|
||||
PRIORITY_MEDIUM = 0.5
|
||||
PRIORITY_LOW = 0.0
|
||||
|
||||
LINEAR_TRANSITION = :linear
|
||||
EASE_IN_OUT_TRANSITION = :ease_in_out
|
||||
|
||||
attr_reader :priority, :title, :tagline, :icon, :time_to_live, :transition_duration, :transition_type
|
||||
def initialize(
|
||||
host:, priority:, title:, title_color: TITLE_COLOR, tagline: "", tagline_color: TAGLINE_COLOR, icon: nil, icon_color: ICON_COLOR,
|
||||
edge_color: EDGE_COLOR, background_color: BACKGROUND_COLOR, time_to_live: TIME_TO_LIVE, transition_duration: TRANSITION_DURATION,
|
||||
transition_type: EASE_IN_OUT_TRANSITION
|
||||
)
|
||||
@host = host
|
||||
|
||||
@priority = priority
|
||||
@title = title
|
||||
@title_color = title_color
|
||||
@tagline = tagline
|
||||
@tagline_color = tagline_color
|
||||
@icon = icon
|
||||
@icon_color = icon_color
|
||||
@edge_color = edge_color
|
||||
@background_color = background_color
|
||||
@time_to_live = time_to_live
|
||||
@transition_duration = transition_duration
|
||||
@transition_type = transition_type
|
||||
|
||||
@icon_scale = ICON_SIZE.to_f / @icon.width if @icon
|
||||
end
|
||||
|
||||
def draw
|
||||
Gosu.draw_rect(0, 0, WIDTH, HEIGHT, @background_color)
|
||||
|
||||
if @host.edge == :top
|
||||
Gosu.draw_rect(0, HEIGHT - EDGE_WIDTH, WIDTH, EDGE_WIDTH, @edge_color)
|
||||
@icon.draw(EDGE_WIDTH + PADDING, PADDING, 0, @icon_scale, @icon_scale, @icon_color) if @icon
|
||||
|
||||
elsif @host.edge == :bottom
|
||||
Gosu.draw_rect(0, 0, WIDTH, EDGE_WIDTH, @edge_color)
|
||||
@icon.draw(EDGE_WIDTH + PADDING, PADDING, 0, @icon_scale, @icon_scale, @icon_color) if @icon
|
||||
|
||||
elsif @host.edge == :right
|
||||
Gosu.draw_rect(0, 0, EDGE_WIDTH, HEIGHT, @edge_color)
|
||||
@icon.draw(EDGE_WIDTH + PADDING, PADDING, 0, @icon_scale, @icon_scale, @icon_color) if @icon
|
||||
|
||||
else
|
||||
Gosu.draw_rect(WIDTH - EDGE_WIDTH, 0, EDGE_WIDTH, HEIGHT, @edge_color)
|
||||
@icon.draw(PADDING, PADDING, 0, @icon_scale, @icon_scale, @icon_color) if @icon
|
||||
end
|
||||
|
||||
icon_space = @icon ? ICON_SIZE + PADDING : 0
|
||||
TITLE_FONT.draw_text(@title, PADDING + EDGE_WIDTH + icon_space, PADDING, 0, 1, 1, @title_color)
|
||||
TAGLINE_FONT.draw_text(@tagline, PADDING + EDGE_WIDTH + icon_space, PADDING + TITLE_FONT.height, 0, 1, 1, @tagline_color)
|
||||
end
|
||||
end
|
||||
end
|
||||
242
lib/cyberarm_engine/notification_manager.rb
Normal file
242
lib/cyberarm_engine/notification_manager.rb
Normal file
@@ -0,0 +1,242 @@
|
||||
module CyberarmEngine
|
||||
class NotificationManager
|
||||
EDGE_TOP = :top
|
||||
EDGE_BOTTOM = :bottom
|
||||
EDGE_RIGHT = :right
|
||||
EDGE_LEFT = :left
|
||||
|
||||
MODE_DEFAULT = :slide
|
||||
MODE_CIRCLE = :circle
|
||||
|
||||
attr_reader :edge, :mode, :max_visible, :notifications
|
||||
def initialize(edge: EDGE_RIGHT, mode: MODE_DEFAULT, window:, max_visible: 1)
|
||||
@edge = edge
|
||||
@mode = mode
|
||||
@window = window
|
||||
@max_visible = max_visible
|
||||
|
||||
@notifications = []
|
||||
@drivers = []
|
||||
@slots = Array.new(max_visible, nil)
|
||||
end
|
||||
|
||||
def draw
|
||||
@drivers.each do |driver|
|
||||
case @edge
|
||||
when :left, :right
|
||||
x = @edge == :right ? @window.width + driver.x : -Notification::WIDTH + driver.x
|
||||
y = driver.y + Notification::HEIGHT / 2
|
||||
|
||||
Gosu.translate(x, y + (Notification::HEIGHT + Notification::PADDING) * driver.slot) do
|
||||
driver.draw
|
||||
end
|
||||
|
||||
when :top, :bottom
|
||||
x = @window.width / 2 - Notification::WIDTH / 2
|
||||
y = @edge == :top ? driver.y - Notification::HEIGHT : @window.height + driver.y
|
||||
slot_position = (Notification::HEIGHT + Notification::PADDING) * driver.slot
|
||||
slot_position *= -1 if @edge == :bottom
|
||||
|
||||
Gosu.translate(x, y + slot_position) do
|
||||
driver.draw
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
show_next_notification if @drivers.size < @max_visible
|
||||
@drivers.each do |driver|
|
||||
if driver.done?
|
||||
@slots[driver.slot] = nil
|
||||
@drivers.delete(driver)
|
||||
end
|
||||
end
|
||||
|
||||
@drivers.each(&:update)
|
||||
end
|
||||
|
||||
def show_next_notification
|
||||
notification = @notifications.sort { |n| n.priority }.reverse.shift
|
||||
return unless notification
|
||||
return if available_slot_index < lowest_used_slot
|
||||
@notifications.delete(notification)
|
||||
|
||||
@drivers << Driver.new(edge: @edge, mode: @mode, notification: notification, slot: available_slot_index)
|
||||
slot = @slots[available_slot_index] = @drivers.last
|
||||
end
|
||||
|
||||
def available_slot_index
|
||||
@slots.each_with_index do |slot, i|
|
||||
return i unless slot
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
def lowest_used_slot
|
||||
@slots.each_with_index do |slot, i|
|
||||
return i if slot
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
def highest_used_slot
|
||||
_slot = -1
|
||||
@slots.each_with_index do |slot, i|
|
||||
_slot = i if slot
|
||||
end
|
||||
|
||||
return _slot
|
||||
end
|
||||
|
||||
def create_notification(**args)
|
||||
notification = Notification.new(host: self, **args)
|
||||
@notifications << notification
|
||||
end
|
||||
|
||||
class Driver
|
||||
attr_reader :x, :y, :notification, :slot
|
||||
def initialize(edge:, mode:, notification:, slot:)
|
||||
@edge = edge
|
||||
@mode = mode
|
||||
@notification = notification
|
||||
@slot = slot
|
||||
|
||||
@x, @y = 0, 0
|
||||
@delta = Gosu.milliseconds
|
||||
@accumulator = 0.0
|
||||
|
||||
@born_at = Gosu.milliseconds
|
||||
@duration_completed_at = Float::INFINITY
|
||||
@transition_completed_at = Float::INFINITY
|
||||
end
|
||||
|
||||
def transition_in_complete?
|
||||
Gosu.milliseconds - @born_at >= @notification.transition_duration
|
||||
end
|
||||
|
||||
def duration_completed?
|
||||
Gosu.milliseconds - @transition_completed_at >= @notification.time_to_live
|
||||
end
|
||||
|
||||
def done?
|
||||
Gosu.milliseconds - @duration_completed_at >= @notification.transition_duration
|
||||
end
|
||||
|
||||
def draw
|
||||
ratio = 0.0
|
||||
|
||||
if not transition_in_complete?
|
||||
ratio = animation_ratio
|
||||
elsif transition_in_complete? and not duration_completed?
|
||||
ratio = 1.0
|
||||
elsif duration_completed?
|
||||
ratio = 1.0 - animation_ratio
|
||||
end
|
||||
|
||||
case @mode
|
||||
when MODE_DEFAULT
|
||||
Gosu.clip_to(0, 0, Notification::WIDTH, Notification::HEIGHT * ratio) do
|
||||
@notification.draw
|
||||
end
|
||||
when MODE_CIRCLE
|
||||
half = Notification::WIDTH / 2
|
||||
|
||||
Gosu.clip_to(half - (half * ratio), 0, Notification::WIDTH * ratio, Notification::HEIGHT) do
|
||||
@notification.draw
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
case @mode
|
||||
when MODE_DEFAULT
|
||||
update_default
|
||||
when MODE_CIRCLE
|
||||
update_circle
|
||||
end
|
||||
|
||||
@accumulator += Gosu.milliseconds - @delta
|
||||
@delta = Gosu.milliseconds
|
||||
end
|
||||
|
||||
|
||||
def update_default
|
||||
case @edge
|
||||
when :left, :right
|
||||
if not transition_in_complete? # Slide In
|
||||
@x = @edge == :right ? -x_offset : x_offset
|
||||
elsif transition_in_complete? and not duration_completed?
|
||||
@x = @edge == :right ? -Notification::WIDTH : Notification::WIDTH if @x.abs != Notification::WIDTH
|
||||
@transition_completed_at = Gosu.milliseconds if @transition_completed_at == Float::INFINITY
|
||||
@accumulator = 0.0
|
||||
elsif duration_completed? # Slide Out
|
||||
@x = @edge == :right ? x_offset - Notification::WIDTH : Notification::WIDTH - x_offset
|
||||
@x = 0 if @edge == :left and @x <= 0
|
||||
@x = 0 if @edge == :right and @x >= 0
|
||||
@duration_completed_at = Gosu.milliseconds if @duration_completed_at == Float::INFINITY
|
||||
end
|
||||
|
||||
when :top, :bottom
|
||||
if not transition_in_complete? # Slide In
|
||||
@y = @edge == :top ? y_offset : -y_offset
|
||||
elsif transition_in_complete? and not duration_completed?
|
||||
@y = @edge == :top ? Notification::HEIGHT : -Notification::HEIGHT if @x.abs != Notification::HEIGHT
|
||||
@transition_completed_at = Gosu.milliseconds if @transition_completed_at == Float::INFINITY
|
||||
@accumulator = 0.0
|
||||
elsif duration_completed? # Slide Out
|
||||
@y = @edge == :top ? Notification::HEIGHT - y_offset : y_offset - Notification::HEIGHT
|
||||
@y = 0 if @edge == :top and @y <= 0
|
||||
@y = 0 if @edge == :bottom and @y >= 0
|
||||
@duration_completed_at = Gosu.milliseconds if @duration_completed_at == Float::INFINITY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_circle
|
||||
case @edge
|
||||
when :top, :bottom
|
||||
@y = @edge == :top ? Notification::HEIGHT : -Notification::HEIGHT
|
||||
when :left, :right
|
||||
@x = @edge == :right ? -Notification::WIDTH : Notification::WIDTH
|
||||
end
|
||||
|
||||
if transition_in_complete? and not duration_completed?
|
||||
@transition_completed_at = Gosu.milliseconds if @transition_completed_at == Float::INFINITY
|
||||
@accumulator = 0.0
|
||||
elsif duration_completed?
|
||||
@duration_completed_at = Gosu.milliseconds if @duration_completed_at == Float::INFINITY
|
||||
end
|
||||
end
|
||||
|
||||
def animation_ratio
|
||||
x = (@accumulator / @notification.transition_duration)
|
||||
|
||||
case @notification.transition_type
|
||||
when Notification::LINEAR_TRANSITION
|
||||
x.clamp(0.0, 1.0)
|
||||
when Notification::EASE_IN_OUT_TRANSITION # https://easings.net/#easeInOutQuint
|
||||
(x < 0.5 ? 16 * x * x * x * x * x : 1 - ((-2 * x + 2) ** 5) / 2).clamp(0.0, 1.0)
|
||||
end
|
||||
end
|
||||
|
||||
def x_offset
|
||||
if not transition_in_complete? or duration_completed?
|
||||
Notification::WIDTH * animation_ratio
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def y_offset
|
||||
if not transition_in_complete? or duration_completed?
|
||||
Notification::HEIGHT * animation_ratio
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user