12 Commits

27 changed files with 1120 additions and 231 deletions

18
Gemfile
View File

@@ -1,3 +1,21 @@
source "https://gem.coop" source "https://gem.coop"
# "standard lib" gems
gem "base64"
gem "rexml"
gem "logger"
gem "json"
# "game" library gem
gem "cyberarm_engine" gem "cyberarm_engine"
gem "sdl2-bindings"
# networking libs
gem "async"
gem "async-http"
gem "async-websocket"
# misc. libs
gem "digest-crc"
gem "ircparser"
gem "rubyzip"

View File

@@ -1,20 +1,125 @@
GEM GEM
remote: https://gem.coop/ remote: https://gem.coop/
specs: specs:
async (2.39.0)
console (~> 1.29)
fiber-annotation
io-event (~> 1.11)
metrics (~> 0.12)
traces (~> 0.18)
async-http (0.95.0)
async (>= 2.10.2)
async-pool (~> 0.11)
io-endpoint (~> 0.14)
io-stream (~> 0.6)
metrics (~> 0.12)
protocol-http (~> 0.62)
protocol-http1 (~> 0.39)
protocol-http2 (~> 0.26)
protocol-url (~> 0.2)
traces (~> 0.10)
async-pool (0.11.2)
async (>= 2.0)
async-websocket (0.30.0)
async-http (~> 0.76)
protocol-http (~> 0.34)
protocol-rack (~> 0.7)
protocol-websocket (~> 0.17)
base64 (0.3.0)
console (1.34.3)
fiber-annotation
fiber-local (~> 1.1)
json
cyberarm_engine (0.25.1) cyberarm_engine (0.25.1)
gosu (~> 1.1) gosu (~> 1.1)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
ffi (1.17.4-x86_64-linux-gnu)
fiber-annotation (0.2.0)
fiber-local (1.1.0)
fiber-storage
fiber-storage (1.0.1)
gosu (1.4.6) gosu (1.4.6)
io-endpoint (0.17.2)
io-event (1.15.1)
io-stream (0.11.1)
ircparser (1.0.0)
json (2.19.4)
logger (1.7.0)
metrics (0.15.0)
protocol-hpack (1.5.1)
protocol-http (0.62.2)
protocol-http1 (0.39.0)
protocol-http (~> 0.62)
protocol-http2 (0.26.0)
protocol-hpack (~> 1.4)
protocol-http (~> 0.62)
protocol-rack (0.22.1)
io-stream (>= 0.10)
protocol-http (~> 0.58)
rack (>= 1.0)
protocol-url (0.4.0)
protocol-websocket (0.21.0)
protocol-http (~> 0.2)
rack (3.2.6)
rake (13.4.2)
rexml (3.4.4)
rubyzip (3.2.2)
sdl2-bindings (0.2.3)
ffi (~> 1.15)
traces (0.18.2)
PLATFORMS PLATFORMS
ruby
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
async
async-http
async-websocket
base64
cyberarm_engine cyberarm_engine
digest-crc
ircparser
json
logger
rexml
rubyzip
sdl2-bindings
CHECKSUMS CHECKSUMS
cyberarm_engine (0.25.1) async (2.39.0) sha256=df18730073f2bbb45788077dfa20cb365ecc1b9453969f44de6796b5191a00aa
gosu (1.4.6) async-http (0.95.0) sha256=08128cab255a48e41d1e856bfa9cbda001f1c57c88bafa3fa3a545ad795754aa
async-pool (0.11.2) sha256=0a43a17b02b04d9c451b7d12fafa9a50e55dc6dd00d4369aca00433f16a7e3ed
async-websocket (0.30.0) sha256=55739954528ad8f87f7792d0452e1268d1ef2aa5b3719f79400a05a1a6202cdf
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
console (1.34.3) sha256=869fbd74697efc4c606f102d2812b0b008e4e7fd738a91c591e8577140ec0dcc
cyberarm_engine (0.25.1) sha256=6f851a2ed0a32bdd25fc619172b546a8e26eefeb13a62c509f226337170d0efe
digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
fiber-annotation (0.2.0) sha256=7abfadf1d119f508867d4103bf231c0354d019cc39a5738945dec2edadaf6c03
fiber-local (1.1.0) sha256=c885f94f210fb9b05737de65d511136ea602e00c5105953748aa0f8793489f06
fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1
gosu (1.4.6) sha256=80c7fa48cf61f4960ef067fdd1c000d98b4adc8e11faeaa486543d00908a5c15
io-endpoint (0.17.2) sha256=3feaf766c116b35839c11fac68b6aaadc47887bb488902a57bf8e1d288fb3338
io-event (1.15.1) sha256=c644cdcf48254015d63f558bf4492f35471f5bb204a42180ea49752be59b30cc
io-stream (0.11.1) sha256=fa5f551fcff99581c1757b9d1cee2c37b124f07d2ca4f40b756a05ab9bd21b87
ircparser (1.0.0) sha256=0d4598f691172e2ad078207c50ba62c0d3428ae841836b0a682e6cd01d62878d
json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
metrics (0.15.0) sha256=61ded5bac95118e995b1bc9ed4a5f19bc9814928a312a85b200abbdac9039072
protocol-hpack (1.5.1) sha256=6feca238b8078da1cd295677d6f306c6001af92d75fe0643d33e6956cbc3ad91
protocol-http (0.62.2) sha256=e1c1f2f56029c1af8c4e2b8a67d0d096c76620f3afd8d99d1dcd2f6b8ffa773b
protocol-http1 (0.39.0) sha256=e49b3f4cda6f5d94c76a323d2b7f6977cba3ebd082d2da437039594da77ad8eb
protocol-http2 (0.26.0) sha256=bac89cd78082b241ccd0cf7246f5160e4bb0c9c975fb4bf7deef5f88cc317486
protocol-rack (0.22.1) sha256=1185d245927ef9849a603700d6991ca353bc89724fbf98efa4a4333ed62a9fc3
protocol-url (0.4.0) sha256=64d4c03b6b51ad815ac6fdaf77a1d91e5baf9220d26becb846c5459dacdea9e1
protocol-websocket (0.21.0) sha256=6e2ccc2adf7de1895b0f6548fdfacf5f9735c0d4deb56cd2bac1ae38a1952e93
rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rubyzip (3.2.2) sha256=c0ed99385f0625415c8f05bcae33fe649ed2952894a95ff8b08f26ca57ea5b3c
sdl2-bindings (0.2.3) sha256=878c0755a4bea1aed0fa85c01596effd1c2b7d1878141ceec5bef808038bd309
traces (0.18.2) sha256=80f1649cb4daace1d7174b81f3b3b7427af0b93047759ba349960cb8f315e214
BUNDLED WITH BUNDLED WITH
4.0.8 4.0.8

View File

@@ -1,7 +1,15 @@
Copyright (c) 2021 cyberarm Copyright (c) 2021-2026 cyberarm
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

30
lib/attribution.rb Normal file
View File

@@ -0,0 +1,30 @@
module W3DHubLauncher
module Attribution
Item = Data.define(:name, :description, :url, :license, :license_url)
SPECIAL_THANKS = [
Item.new("W3D Hub", "Creating awesome games and mods for free", "https://w3dhub.com", "", ""),
Item.new("Yukihiro \"matz\" Matsumoto", "Creating the Ruby language", "https://matz.rubyist.net", "", ""),
Item.new("Samuel \"ioquatix\" Williams", "Creating and maintaining the async libraries for Ruby", "https://www.codeotaku.com", "", ""),
Item.new("Kenney", "Creating awesome game assets and releasing them completely for free\nThe launcher uses their UI Icon pack", "https://kenney.nl", "CC0", "https://creativecommons.org/publicdomain/zero/1.0/"),
]
LIBRARIES = [
Item.new("Ruby", "Programming language. A Programmer's Best Friend", "https://ruby-lang.org", "BSD 2-Clause", "https://www.ruby-lang.org/en/about/license.txt"),
Item.new("gosu", "Light-weight game library", "https://libgosu.org", "MIT", "https://github.com/gosu/gosu/blob/master/COPYING"),
Item.new("SDL2", "Simple DirectMedia Layer", "https://libsdl.org", "MIT", "https://github.com/libsdl-org/SDL/blob/SDL2/LICENSE.txt"),
Item.new("MojoAL", "OpenAL sound library implementation in a single C file", "https://icculus.org/mojoAL/", "MIT", "https://github.com/icculus/mojoAL/blob/main/LICENSE.txt"),
Item.new("async", "Asynchronous event-driven reactor for Ruby", "https://github.com/socketry/async", "MIT", "https://github.com/socketry/async/blob/main/license.md"),
Item.new("async-http", "Asynchronous http(s) client/server for Ruby", "https://github.com/socketry/async-http", "MIT", "https://github.com/socketry/async-http/blob/main/license.md"),
Item.new("async-websocket", "Asynchronous websockets for Ruby", "https://github.com/socketry/async-websocket", "MIT", "https://github.com/socketry/async-websocket/blob/main/license.md"),
Item.new("rubyzip", "Ruby library for reading and writing zip files", "https://github.com/rubyzip/rubyzip", "BSD 2-Clause", "https://github.com/rubyzip/rubyzip/blob/main/LICENSE.md"),
Item.new("digest-crc", "Ruby library for reading and writing zip files", "https://github.com/postmodern/digest-crc", "MIT", "https://github.com/postmodern/digest-crc/blob/main/LICENSE.txt"),
Item.new("ircparser", "Ruby parser for the IRCv3 message format", "https://codeberg.org/sadiepowell/ircparser-ruby", "MIT", "https://codeberg.org/sadiepowell/ircparser-ruby#license"),
Item.new("base64", "Ruby library for encoding and decoding base64", "https://github.com/ruby/base64", "BSD 2-Clause", "https://github.com/ruby/base64/blob/master/COPYING"),
Item.new("rexml", "Ruby library for parsing XML", "https://github.com/ruby/rexml", "BSD 2-Clause", "https://github.com/ruby/rexml/blob/master/LICENSE.txt"),
Item.new("logger", "Ruby logging library", "https://github.com/ruby/logger", "BSD 2-Clause", "https://github.com/ruby/logger/blob/master/COPYING"),
]
end
end

11
lib/constants.rb Normal file
View File

@@ -0,0 +1,11 @@
module W3DHubLauncher
DIRECTORY_NAME = "w3d-hub-linux-launcher".freeze
ROOT_PATH = Dir.pwd
CONFIG_PATH = "#{Dir.home}/.config/#{DIRECTORY_NAME}".freeze
CACHE_PATH = "#{Dir.home}/.cache/#{DIRECTORY_NAME}".freeze
DEFAULT_PACKAGE_CACHE_PATH = "#{CACHE_PATH}/packages".freeze
DEFAULT_APPLICATIONS_PATH = "#{Dir.home}/.local/share/#{DIRECTORY_NAME}/applications".freeze
USER_AGENT = "#{NAME} v#{VERSION}".freeze
end

9
lib/dialog.rb Normal file
View File

@@ -0,0 +1,9 @@
module W3DHubLauncher
class Dialog < CyberarmEngine::Dialog
def setup
theme(THEME)
background 0xee_222222
end
end
end

View File

@@ -1,12 +1,51 @@
module W3DHubLauncher module W3DHubLauncher
module Dialogs class Dialog
class About < CyberarmEngine::GuiState # Dialog class About < W3DHubLauncher::Dialog
def setup def setup
# application name and version super
# authorship
# special thanks stack(width: 1.0, max_width: 600, height: 1.0, max_height: 600, h_align: :center, v_align: :center, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK) do
# used gems and libraries flow(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do
# useful links banner "About", width: 1.0, text_align: :center
end
stack(width: 1.0, fill: true, scroll: true, padding_left: PADDING, padding_right: PADDING) do
title NAME
tagline format("v%s (%s)", VERSION, VERSION_NAME), margin_left: PADDING
para "© 2021 - #{Time.now.year} cyberarm", margin_left: PADDING
link "MIT licence", margin_left: PADDING
title "Special Thanks", margin_top: LARGE_PADDING
W3DHubLauncher::Attribution::SPECIAL_THANKS.each do |item|
present_item(item)
end
title "Software / Libraries", margin_top: LARGE_PADDING
W3DHubLauncher::Attribution::LIBRARIES.each do |item|
present_item(item)
end
end
flow(width: 1.0, padding: PADDING, background_nine_slice: NINE_SLICE_ROUNDED_BOTTOM, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do
flow(fill: true)
button "Close" do
pop_state
end
flow(fill: true)
end
end
end
def present_item(item)
unless item.url.empty?
link item.name, tip: item.url, margin_left: PADDING, font: FONT_BOLD
else
tagline item.name, margin_left: PADDING
end
para item.description, margin_left: LARGE_PADDING
link item.license, tip: item.license_url, margin_left: PADDING + LARGE_PADDING unless item.license.empty?
end end
end end
end end

38
lib/dialogs/downloads.rb Normal file
View File

@@ -0,0 +1,38 @@
module W3DHubLauncher
class Dialog
class Downloads < W3DHubLauncher::Dialog
def setup
super
stack(width: 1.0, max_width: 600, height: 1.0, max_height: 600, h_align: :center, v_align: :center, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK) do
flow(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do
banner "Downloads", width: 1.0, text_align: :center
end
stack(width: 1.0, fill: true, scroll: true, padding_left: PADDING, padding_right: PADDING) do
50.times do
stack(width: 1.0, margin_top: HALF_PADDING, margin_bottom: HALF_PADDING) do
flow(width: 1.0) do
para "PACKAGE_NAME.mix"
flow(fill: true)
para ["Downloading...", "Pending...", "Done.", "Patching...", "Unpacking..."].sample
end
progress(width: 1.0, fraction: rand)
end
end
end
flow(width: 1.0, padding: PADDING, background_nine_slice: NINE_SLICE_ROUNDED_BOTTOM, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do
flow(fill: true)
button "Close" do
pop_state
end
flow(fill: true)
end
end
end
end
end
end

View File

@@ -6,14 +6,14 @@ module W3DHubLauncher
def safe_get_image(path, retro: false) def safe_get_image(path, retro: false)
return get_image(path, retro: retro) if File.exist?(path) return get_image(path, retro: retro) if File.exist?(path)
path = "./media/default.png" path = "#{ROOT_PATH}/media/default.png"
return get_image(path, retro: retro) if File.exist?(path) return get_image(path, retro: retro) if File.exist?(path)
WHITE_IMAGE WHITE_IMAGE
end end
def rounded_avatar(image) def rounded_avatar(image)
circle = get_image("./media/ui/circle.png") circle = get_image("#{ROOT_PATH}/media/ui/circle.png")
scale = [(circle.width.to_f / image.width).abs, (circle.width.to_f / image.height).abs].min scale = [(circle.width.to_f / image.width).abs, (circle.width.to_f / image.height).abs].min
Gosu.render(circle.width, circle.height) do Gosu.render(circle.width, circle.height) do

View File

@@ -0,0 +1,68 @@
module W3DHubLauncher
module Page
module Boot
class InitialSetup < CyberarmEngine::Page
include GuiExt
def setup
stack(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: 8, background_nine_slice_color: 0x11_ffffff, padding: HALF_PADDING) do
banner "Welcome to #{NAME}", width: 1.0, text_align: :center
tagline "Your gateway to the world of W3D Hub games.", width: 1.0, text_align: :center
end
stack(width: 1.0, fill: true, padding: PADDING) do
stack(width: 1.0, fill: true, scroll: true) do
title "Initial Setup"
caption "Please confirm launcher's default settings and make any desired tweaks.", font: FONT_REGULAR
flow(width: 1.0, height: 40, margin_top: HALF_PADDING) do
tagline "Launcher package cache directory", height: 1.0, text_v_align: :center
edit_line DEFAULT_PACKAGE_CACHE_PATH, fill: true
button "Browse..."
end
inscription "Location where the launcher will download application packages."
flow(width: 1.0, height: 40, margin_top: HALF_PADDING) do
tagline "Application installation directory", height: 1.0, text_v_align: :center
edit_line DEFAULT_APPLICATIONS_PATH, fill: true
button "Browse..."
end
inscription "Location where the launcher will install new applications."
flow(width: 1.0, height: 40, margin_top: HALF_PADDING) do
tagline "Wine context", height: 1.0, text_v_align: :center
edit_line "", fill: true
button "Browse..."
end
inscription "Location of wine context to use. Leave blank to use default."
flow(width: 1.0, height: 40, margin_top: HALF_PADDING) do
tagline "Wine command", height: 1.0, text_v_align: :center
edit_line "wine", fill: true
button "Browse..."
end
inscription "Path to wine executable. Use `wine` for system installed wine."
end
flow(width: 1.0, padding_top: PADDING) do
flow(fill: true)
button "Accept" do |btn|
btn.enabled = false
Worker::Api.update_settings({}) do |result|
if result.okay?
page(Page::Boot::StartUp)
else
btn.enabled = true
end
puts "NOW!"
end
end
end
end
end
end
end
end
end

View File

@@ -0,0 +1,82 @@
module W3DHubLauncher
module Page
module Boot
class StartUp < CyberarmEngine::Page
include GuiExt
def setup
stack(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: 8, background_nine_slice_color: 0x11_ffffff, padding: HALF_PADDING) do
banner "Establishing Battlefield Control", width: 1.0, text_align: :center
tagline "Please stand by...", width: 1.0, text_align: :center
end
stack(width: 1.0, fill: true, padding: PADDING) do
stack(width: 1.0, fill: true, scroll: true) do
a = flow(width: 1.0, height: HALF_PADDING + PADDING, visible: false) do
image safe_get_image("#{ROOT_PATH}/media/icons/checkmark.png"), height: 1.0, v_align: :center
tagline "DNS resolution", height: 1.0, text_v_align: :center
end
after(500) do
a.show
end
b = flow(width: 1.0, height: HALF_PADDING + PADDING, visible: false) do
image safe_get_image("#{ROOT_PATH}/media/icons/information.png"), height: 1.0, v_align: :center, color: 0xff_ff8800
tagline "Upstream backend", height: 1.0, text_v_align: :center
caption "Notice: Unreachable. Accounts will be unavailable.", height: 1.0, text_v_align: :center, color: 0xff_ff8800
end
after(1000) do
b.show
end
c = flow(width: 1.0, height: HALF_PADDING + PADDING, visible: false) do
image safe_get_image("#{ROOT_PATH}/media/icons/checkmark.png"), height: 1.0, v_align: :center
tagline "Alternate backend", height: 1.0, text_v_align: :center
end
after(1500) do
c.show
end
d = flow(width: 1.0, height: HALF_PADDING + PADDING, visible: false) do
image safe_get_image("#{ROOT_PATH}/media/icons/information.png"), height: 1.0, v_align: :center, color: 0xff_ff8800
tagline "Refresh account session", height: 1.0, text_v_align: :center
caption "Notice: Upstream backend unavailable, session not refreshed.", height: 1.0, text_v_align: :center, color: 0xff_ff8800
end
after(2000) do
d.show
end
e = flow(width: 1.0, height: HALF_PADDING + PADDING, visible: false) do
image safe_get_image("#{ROOT_PATH}/media/icons/checkmark.png"), height: 1.0, v_align: :center
tagline "Fetch game servers", height: 1.0, text_v_align: :center
end
after(2500) do
e.show
end
f = flow(width: 1.0, height: HALF_PADDING + PADDING, visible: false) do
image safe_get_image("#{ROOT_PATH}/media/icons/cross.png"), height: 1.0, v_align: :center, color: 0xff_ff0000
tagline "Fetch applications", height: 1.0, text_v_align: :center
caption "Fatal: Failed to retrieve applications list and no local cache exists. Cannot continue.", height: 1.0, text_v_align: :center, color: 0xff_ff0000
end
after(3000) do
f.show
@progress_bar.type = :linear
@progress_bar.value = 0.0
end
after(3500) do
parent.page_host.clear do
banner "Battlefield control established".upcase, width: 1.0, height: 1.0, text_v_align: :center, text_align: :center
end
end
after(3600) do
pop_state
push_state(States::Interface)
end
end
flow(width: 1.0, padding_top: PADDING) do
@progress_bar = progress width: 1.0, type: :marquee
end
end
end
end
end
end
end

38
lib/pages/boot/terms.rb Normal file
View File

@@ -0,0 +1,38 @@
module W3DHubLauncher
module Page
module Boot
class Terms < CyberarmEngine::Page
include GuiExt
TERMS_AND_CONDITIONS = <<~TERMS
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
TERMS
def setup
flow(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: 8, background_nine_slice_color: 0x11_ffffff, padding: HALF_PADDING) do
banner "Terms and Conditions", width: 1.0, text_align: :center
end
stack(width: 1.0, fill: true, padding: PADDING) do
stack(width: 1.0, fill: true, scroll: true, padding_bottom: PADDING) do
tagline TERMS_AND_CONDITIONS, font: FONT_REGULAR
end
flow(width: 1.0) do
flow(fill: true)
button "Decline", margin_right: PADDING do
window.close
end
button "Accept" do
page(Page::Boot::InitialSetup)
end
end
end
end
end
end
end
end

View File

@@ -0,0 +1,82 @@
module W3DHubLauncher
module Page
class Games < CyberarmEngine::Page
include GuiExt
def setup
# game bar container
flow(width: 1.0, height: 60) do
widget(width: 220, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY, hover: { background_nine_slice_color: ALPHA_BLACK }, active: { background_nine_slice_color: ALPHA_GRAY }) do
flow(width: 1.0, height: 40, margin_left: PADDING, v_align: :center, h_align: :center) do
image safe_get_image("#{ROOT_PATH}/media/icons/menuGrid.png"), height: 40, color: 0xff_bbbbbb
link "ALL GAMES", text_size: 24, font: FONT_BLACK, height: 1.0, text_v_align: :center
end
end
flow(fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY, margin_left: PADDING) do
image safe_get_image("#{ROOT_PATH}/data/cache/apb.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0x88_5e5c64, border_thickness_bottom: 3, border_color_bottom: 0xff_3584e4, tip: "Red Alert: A Path Beyond"
image safe_get_image("#{ROOT_PATH}/data/cache/ren.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Command & Conquer: Renegade"
image safe_get_image("#{ROOT_PATH}/data/cache/tsr.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Tiberian Sun: Reborn"
image safe_get_image("#{ROOT_PATH}/data/cache/woa.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Battle for Dune: War of Assassins"
end
end
# game content container
flow(width: 1.0, fill: true, margin_top: LARGE_PADDING) do
# game info container
stack(width: 340, height: 1.0) do
# logo
image safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/logo.png.package"), width: 1.0, max_height: 124
# web links
stack(width: 1.0, fill: true, padding: 0, padding_top: LARGE_PADDING) do
link "Modifications", text_size: 24
link "Bug Tracker", text_size: 24
link "Player Statistics", text_size: 24
end
# launching ta game
caption "Game Version"
list_box items: [ "Release", "Open Testing" ], width: 1.0, margin_bottom: PADDING
flow(width: 1.0, height: 60) do
button "PLAY", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **CTA_BUTTON_THEME
button safe_get_image("#{ROOT_PATH}/media/icons/singleplayer.png"), image_height: 1.0, background_nine_slice: NINE_SLICE_SQUARE, **CTA_BUTTON_THEME
button safe_get_image("#{ROOT_PATH}/media/icons/gear.png"), image_height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_RIGHT, **CTA_BUTTON_THEME
end
inscription "Version: 3.9.2.15", margin_top: PADDING
end
# game events and news container
stack(fill: true, height: 1.0, margin_left: LARGE_PADDING, scroll: true) do
flow(width: 1.0, height: 1.0, max_height: 380, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do
image safe_get_image("#{ROOT_PATH}/media/background.png"), fill: true, aspect_ratio: 16.0 / 9.0
stack(fill: true, height: 1.0, margin_left: PADDING) do
caption "Upcoming Event".upcase, color: 0xff_22aa11
title "Red Alert: A Path Beyond Game Night"
tagline "July 11, 2028"
flow(fill: true)
button "Read More", margin_left: PADDING, margin_right: LARGE_PADDING, margin_bottom: PADDING, width: 1.0
end
end
# news container
flow(width: 1.0, margin_top: PADDING) do
9.times do
stack(width: 1.0 / 3, min_width: 345, height: 345, aspect_ratio: 1, margin_left: HALF_PADDING, margin_right: HALF_PADDING, margin_bottom: PADDING) do
stack(width: 1.0, fill: true, background_image: safe_get_image("#{ROOT_PATH}/media/background.png"), background_image_mode: :fill)
stack(width: 1.0, height: 1.0 / 3, padding: PADDING, v_align: :bottom, background_nine_slice: NINE_SLICE_ROUNDED_BOTTOM, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY, border_thickness_top: 1, border_color_top: Gosu::Color::BLACK) do
caption "NEWS", color: 0x88_ffffff
tagline "A News Item Post A News Item Post"
end
end
end
end
end
end
end
end
end
end

View File

@@ -0,0 +1,71 @@
module W3DHubLauncher
module Page
class ServerBrowser < CyberarmEngine::Page
include GuiExt
def setup
# game bar container
flow(width: 1.0, height: 60) do
flow(width: 220, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do
flow(width: 1.0, height: 40, margin_left: PADDING, v_align: :center, h_align: :center) do
image safe_get_image("#{ROOT_PATH}/media/icons/menuGrid.png"), height: 40, color: 0xff_bbbbbb
link "ALL SERVERS", text_size: 24, font: FONT_BLACK, height: 1.0, text_v_align: :center
end
end
flow(fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY, margin_left: PADDING) do
image safe_get_image("#{ROOT_PATH}/data/cache/apb.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0x88_5e5c64, border_thickness_bottom: 3, border_color_bottom: 0xff_3584e4, tip: "Red Alert: A Path Beyond"
image safe_get_image("#{ROOT_PATH}/data/cache/ren.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Command & Conquer: Renegade"
image safe_get_image("#{ROOT_PATH}/data/cache/tsr.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Tiberian Sun: Reborn"
image safe_get_image("#{ROOT_PATH}/data/cache/woa.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Battle for Dune: War of Assassins"
end
end
# game content container
stack(width: 1.0, fill: true, margin_top: LARGE_PADDING, scroll: true) do
10.times do
widget(width: 1.0, height: 48, padding_top: HALF_PADDING, padding_bottom: HALF_PADDING, margin_bottom: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY, hover: { background_nine_slice_color: 0xff_5e5c64 } , active: { background_nine_slice_color: 0xaa_5e5c64 }) do
# app icon container
image(safe_get_image("#{ROOT_PATH}/data/cache/apb.png"), tip: "Red Alert: A Path Beyond", width: 48, height: 1.0, margin_left: HALF_PADDING)
# friend name and status container
stack(fill: true, height: 1.0, margin_left: HALF_PADDING) do
stack(v_align: :center) do
# server name
server_name = ["Really Long Server Name Goes Here", "[US][W3D Hub] APB Game Night", "[US][W3D Hub] APB Co-op 3.7", "Really Long Server Name Goes Here Really Long Server Name Goes Here"].sample
caption server_name, text_wrap: :none, tip: server_name
# server info
a = ["North America", "South America", "Europe", "Asia", "Antarctica", "Arctica", "Oceania"]
inscription "#{a.sample} • 13:52 / #{rand > 0.5 ? '∞' : '30:00'}", text_wrap: :none, margin_top: -HALF_PADDING
end
end
# server map
stack(width: 256, height: 1.0, margin_left: HALF_PADDING) do
stack(width: 1.0, fill: true, v_align: :center) do
server_map = ["RA_Under", "C&C Superduple Long Map Name Goes Here", "RA_NorthByNorthWest", "RA_HostileWatersParadox", "RA_PacificThreat"].sample
caption server_map, tip: server_map, text_wrap: :none, width: 1.0, text_align: :center
inscription "map", text_wrap: :none, width: 1.0, text_align: :center, margin_top: -HALF_PADDING
end
end
# server player count
stack(width: 96, height: 1.0, margin_left: HALF_PADDING) do
stack(width: 1.0, fill: true, v_align: :center) do
caption format("%d / %d", rand(60), rand(60..127)), width: 1.0, text_align: :center
inscription "players", text_wrap: :none, width: 1.0, text_align: :center, margin_top: -HALF_PADDING
end
end
# server ping
flow(width: 96, height: 1.0, margin_left: HALF_PADDING, margin_right: HALF_PADDING) do
stack(fill: true, height: 1.0, v_align: :center) do
caption rand > 0.85 ? "?" : format("%d ms", rand(16..360)), width: 1.0, text_align: :center
inscription "ping", text_wrap: :none, width: 1.0, text_align: :center, margin_top: -HALF_PADDING
end
stack(width: 8, height: rand(0.25..1.0), v_align: :center, min_height: 8, background_nine_slice: NINE_SLICE_ROUNDED_SMALL, background_nine_slice_from_edge: NINE_SLICE_EDGE_SMALL, background_nine_slice_color: [0xff_26a269, 0xff_e5a50a, 0xff_a51d2d, 0xff_3d3846].sample)
end
end
end
end
end
end
end
end

9
lib/state.rb Normal file
View File

@@ -0,0 +1,9 @@
module W3DHubLauncher
class State < CyberarmEngine::GuiState
include W3DHubLauncher::GuiExt
def setup
theme(THEME)
end
end
end

View File

@@ -0,0 +1,35 @@
module W3DHubLauncher
class States
class Boot < W3DHubLauncher::State
def setup
super
# root container - background image
stack(width: 1.0, height: 1.0, background_image: safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/background.png.package"), background_image_mode: :fill) do
# root container - background image tint
flow(width: 1.0, height: 1.0, background: 0xaa_000000) do
# content container
stack(fill: true, height: 1.0, margin: PADDING, margin_right: PADDING) do
# header bar container
flow(width: 1.0, height: 80, margin_bottom: PADDING) do |c|
# logo image
image(safe_get_image("#{ROOT_PATH}/media/logo.png"), height: 1.0)
stack(fill: true, height: 1.0) do
stack(fill: true)
title NAME
stack(fill: true)
end
end
@page_host = stack(width: 1.0, fill: true, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: 8, background_nine_slice_color: ALPHA_GRAY) do
end
end
end
end
page(Page::Boot::Terms)
end
end
end
end

View File

@@ -0,0 +1,139 @@
module W3DHubLauncher
class States
class Interface < W3DHubLauncher::State
def setup
super
# root container - background image
stack(width: 1.0, height: 1.0, background_image: safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/background.png.package"), background_image_mode: :fill) do
# root container - background image tint
stack(width: 1.0, height: 1.0, background: ALPHA_BLACK) do
# content container
stack(width: 1.0, fill: true, margin: PADDING) do
# header bar container
flow(width: 1.0, height: 80, margin_bottom: PADDING) do |c|
# logo + menu button
button(safe_get_image("#{ROOT_PATH}/media/logo.png"), image_height: 1.0, background: 0, border_color: 0, hover: { background: 0 }, active: { background: 0, color: 0xff_ffffff }) do |btn|
menu(parent: btn) do
menu_item("Settings")
menu_item("About") do
dialog(Dialog::About)
end
menu_item("Exit") do
window.close
end
end.show
end
# navigation bar container
stack(fill: true, height: 1.0) do
# stack(fill: true)
# navigation container
flow(width: 1.0) do
link("GAMES", text_v_align: :center, font: FONT_BLACK, margin_left: PADDING) { page(Page::Games) }
link("SERVERS", text_v_align: :center, font: FONT_BLACK, margin_left: PADDING) { page(Page::ServerBrowser) }
stack(fill: true)
image safe_get_image("#{ROOT_PATH}/media/icons/import.png"), height: 40, color: 0xff_bbbbbb, tip: "Downloads" do |img|
dialog(Dialog::Downloads)
end
image safe_get_image("#{ROOT_PATH}/media/icons/information.png"), height: 40, color: 0xff_bbbbbb, tip: "Notifications"
end
# application task status bar container
stack(width: 1.0, fill: true, margin_left: PADDING) do
flow(width: 1.0) do
para "Updating Red Alert: A Path Beyond (Release)"
stack(fill: true)
para "Fetching manifests..."
end
progress(width: 1.0, fraction: 0.75)
end
end
# self account container
flow(width: 300, height: 80, margin_left: LARGE_PADDING) do
# self avatar container
stack(width: 80, height: 1.0, background_image: rounded_avatar(safe_get_image("#{ROOT_PATH}/media/default.png"))) do
# self online state container
stack(width: 20, height: 20, v_align: :bottom, h_align: :right, background_image: safe_get_image("#{ROOT_PATH}/media/ui/circle_small.png"), background_image_color: 0xff_26a269)
end
stack(fill: true, height: 1.0, margin_left: HALF_PADDING) do
flow(fill: true)
# self name
caption "cyberarm", font: FONT_BLACK, text_wrap: :none
# self set online state
link "Online ▼", text_size: 18 do |l|
menu(parent: l) do
menu_item("Online")
menu_item("Do Not Disturb")
menu_item("Away")
menu_item("Invisible")
menu_item("Sign Out")
end.show
end
flow(fill: true)
end
end
end
# layout container
flow(width: 1.0, fill: true) do
# page host container
@page_host = stack(fill: true, height: 1.0) do
end
# battleview/friends container
@battleview_container = stack(width: 300, height: 1.0, margin_left: LARGE_PADDING, visible: true) do
# friend management container
flow(width: 1.0, height: 60) do
flow(width: 1.0, v_align: :center) do
button safe_get_image("#{ROOT_PATH}/media/icons/singleplayer.png"), image_height: 1.0
button safe_get_image("#{ROOT_PATH}/media/icons/gear.png"), image_height: 1.0, margin_left: HALF_PADDING
edit_line "", margin_left: HALF_PADDING, fill: true, height: 1.0
end
end
# friends/clanmates list container
stack(width: 1.0, fill: true, margin_top: LARGE_PADDING, scroll: true) do
50.times do |i|
# friend container
widget(width: 1.0, height: 48, padding_top: HALF_PADDING, padding_bottom: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, hover: { background_nine_slice_color: ALPHA_GRAY }, active: { background_nine_slice_color: ALPHA_BLACK }) do |w|
w.subscribe(:clicked_left_mouse_button) do
puts "HELLO THERE"
end
# friend avatar container
stack(width: 48, height: 1.0, margin_left: HALF_PADDING, background_image: rounded_avatar(safe_get_image("#{ROOT_PATH}/media/default.png"))) do
stack(width: 12, height: 12, v_align: :bottom, h_align: :right, background_image: safe_get_image("#{ROOT_PATH}/media/ui/circle_small.png"), background_image_color: 0xff_26a269)
end
# friend name and status container
stack(fill: true, height: 1.0, margin_left: HALF_PADDING, margin_right: HALF_PADDING) do
stack(v_align: :center) do
caption ["Silverlight", "PXD2000", "Alstar", "SteelGhost", "FRAYDO"].sample, text_wrap: :none
inscription "RA_Under • 13:52", text_wrap: :none, margin_top: -HALF_PADDING
end
end
# friend active application container
stack(width: 48, height: 1.0, margin_right: HALF_PADDING, background_image: safe_get_image("#{ROOT_PATH}/media/logo.png"))
end
end
end
end
end
end
end
end
page(Page::Games)
end
def button_up(id)
super
@battleview_container.toggle if id == Gosu::KB_F8
end
end
end
end

View File

@@ -3,24 +3,38 @@ module W3DHubLauncher
PADDING = 20 PADDING = 20
HALF_PADDING = 10 HALF_PADDING = 10
ALPHA_BLACK = 0x88_000000 ALPHA_BLACK = 0x88_000000
ALPHA_GRAY = 0x88_5e5c64
FONT_LIGHT = "./media/fonts/NotoSans-Light.ttf" FONT_LIGHT = "#{ROOT_PATH}/media/fonts/NotoSans-Light.ttf"
FONT_REGULAR = "./media/fonts/NotoSans-Regular.ttf" FONT_REGULAR = "#{ROOT_PATH}/media/fonts/NotoSans-Regular.ttf"
FONT_BOLD = "./media/fonts/NotoSans-Bold.ttf" FONT_BOLD = "#{ROOT_PATH}/media/fonts/NotoSans-Bold.ttf"
FONT_BLACK = "./media/fonts/NotoSans-Black.ttf" FONT_BLACK = "#{ROOT_PATH}/media/fonts/NotoSans-Black.ttf"
FONT_MONO = "./media/fonts/NotoSansMono-Regular.ttf" FONT_MONO = "#{ROOT_PATH}/media/fonts/NotoSansMono-Regular.ttf"
NINE_SLICE_EDGE = 8 NINE_SLICE_EDGE = 8
NINE_SLICE_ROUNDED = "./media/ui/rounded.png" NINE_SLICE_EDGE_SMALL = 4
NINE_SLICE_ROUNDED_LEFT = "./media/ui/rounded_left.png" NINE_SLICE_EDGE_TINY = 2
NINE_SLICE_ROUNDED_RIGHT = "./media/ui/rounded_right.png" NINE_SLICE_ROUNDED = "#{ROOT_PATH}/media/ui/rounded.png"
NINE_SLICE_ROUNDED_TOP = "./media/ui/rounded_top.png" NINE_SLICE_ROUNDED_SMALL = "#{ROOT_PATH}/media/ui/rounded_small.png"
NINE_SLICE_ROUNDED_BOTTOM = "./media/ui/rounded_bottom.png" NINE_SLICE_ROUNDED_TINY = "#{ROOT_PATH}/media/ui/rounded_small.png"
NINE_SLICE_SQUARE = "./media/ui/square.png" NINE_SLICE_ROUNDED_LEFT = "#{ROOT_PATH}/media/ui/rounded_left.png"
NINE_SLICE_ROUNDED_RIGHT = "#{ROOT_PATH}/media/ui/rounded_right.png"
NINE_SLICE_ROUNDED_TOP = "#{ROOT_PATH}/media/ui/rounded_top.png"
NINE_SLICE_ROUNDED_BOTTOM = "#{ROOT_PATH}/media/ui/rounded_bottom.png"
NINE_SLICE_SQUARE = "#{ROOT_PATH}/media/ui/square.png"
CTA_BUTTON_THEME = { CTA_BUTTON_THEME = {
background_nine_slice_color: 0xff_1a5fb4 color: 0xff_ffffff,
background_nine_slice_color: 0xff_1c71d8,
hover: {
color: 0xff_ffffff,
background_nine_slice_color: 0xff_3584e4
},
active: {
color: 0xff_ffffff,
background_nine_slice_color: 0xff_1a5fb4
}
} }
THEME = { THEME = {
@@ -40,6 +54,24 @@ module W3DHubLauncher
color: 0xff_888888 color: 0xff_888888
} }
}, },
Inscription: {
text_size: 18
},
Para: {
text_size: 20
},
Caption: {
font: FONT_BOLD
},
Tagline: {
font: FONT_BOLD
},
Title: {
font: FONT_BOLD
},
Banner: {
font: FONT_BLACK
},
Button: { Button: {
font: FONT_BOLD, font: FONT_BOLD,
text_shadow: false, text_shadow: false,
@@ -68,6 +100,7 @@ module W3DHubLauncher
text_align: :left, text_align: :left,
text_size: 24, text_size: 24,
font: FONT_REGULAR, font: FONT_REGULAR,
padding_left: HALF_PADDING
# background: 0xaa_000000, # background: 0xaa_000000,
# border_color: 0xff_000000, # border_color: 0xff_000000,
# hover: { # hover: {
@@ -95,13 +128,24 @@ module W3DHubLauncher
background: 0xee_444444 background: 0xee_444444
} }
}, },
Progress: {
height: NINE_SLICE_EDGE_TINY * 2,
background: 0,
background_nine_slice: NINE_SLICE_ROUNDED_TINY,
background_nine_slice_from_edge: NINE_SLICE_EDGE_TINY,
background_nine_slice_mode: :stretched,
background_nine_slice_color: 0x88_5e5c64,
fraction_background: 0xff_1a5fb4,
border_thickness: 0,
},
ToolTip: { ToolTip: {
delay: 500,
text_size: 24, text_size: 24,
background: 0,#x88_5e5c64, background: 0,#x88_5e5c64,
background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice: NINE_SLICE_ROUNDED,
background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_from_edge: NINE_SLICE_EDGE,
background_nine_slice_mode: :stretched, background_nine_slice_mode: :stretched,
background_nine_slice_color: 0xdd_5e5c64, background_nine_slice_color: 0xcc_000000,
border_thickness: 0 border_thickness: 0
} }
} }

View File

@@ -1,5 +1,5 @@
module W3DHubLauncher module W3DHubLauncher
NAME = "Cyberarm's W3D Hub linux Launcher".freeze NAME = "Cyberarm's W3D Hub Linux Launcher".freeze
VERSION = "2.0.0".freeze VERSION = "2.0.0".freeze
VERSION_NAME = "alpha".freeze # TODO: Come up with a short, fun, release name VERSION_NAME = "alpha".freeze # TODO: Come up with a short, fun, release name
end end

View File

@@ -4,186 +4,12 @@ module W3DHubLauncher
self.show_cursor = true self.show_cursor = true
self.caption = format("%s | v%s (%s)", NAME, VERSION, VERSION_NAME) # "Cyberarm's W3D Hub Linux Launcher | v2.0.0 alpha" self.caption = format("%s | v%s (%s)", NAME, VERSION, VERSION_NAME) # "Cyberarm's W3D Hub Linux Launcher | v2.0.0 alpha"
push_state(Interface) push_state(States::Boot)
# push_state(States::Interface)
end end
end
class Interface < CyberarmEngine::GuiState def needs_redraw?
include W3DHubLauncher::GuiExt states.any?(&:needs_repaint?)
def setup
theme(THEME)
# root container - background image
stack(width: 1.0, height: 1.0, background_image: safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/background.png.package"), background_image_mode: :fill) do
# root container - background image tint
flow(width: 1.0, height: 1.0, background: 0xaa_000000) do
# content container
stack(fill: true, height: 1.0, margin: PADDING, margin_right: LARGE_PADDING) do
# header bar container
flow(width: 1.0, height: 80, margin_bottom: PADDING) do |c|
# logo + menu button
button(safe_get_image("./media/logo.png"), image_height: 1.0, background: 0, border_color: 0, hover: { background: 0 }, active: { background: 0, color: 0xff_ffffff }) do |btn|
menu(parent: btn) do
menu_item("Settings")
menu_item("About")
menu_item("Exit") do
window.close
end
end.show
end
stack(fill: true, height: 1.0) do
stack(fill: true)
flow(width: 1.0) do
link "GAMES", text_v_align: :center, font: FONT_BLACK, margin_left: PADDING
link "SERVERS", text_v_align: :center, font: FONT_BLACK, margin_left: PADDING
stack(fill: true)
image safe_get_image("./media/icons/import.png"), height: 40, color: 0xff_bbbbbb
image safe_get_image("./media/icons/information.png"), height: 40, color: 0xff_bbbbbb
end
stack(fill: true)
end
end
# page content container
stack(width: 1.0, fill: true) do
# game bar container
flow(width: 1.0, height: 60) do
flow(width: 220, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK) do
flow(width: 1.0, height: 40, margin_left: PADDING, v_align: :center, h_align: :center) do
image safe_get_image("./media/icons/menuGrid.png"), height: 40, color: 0xff_bbbbbb
link "ALL GAMES", text_size: 24, font: FONT_BLACK, height: 1.0, text_v_align: :center
end
end
flow(fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK, margin_left: PADDING) do
image safe_get_image("./data/cache/apb.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0x88_5e5c64, border_thickness_bottom: 3, border_color_bottom: 0xff_3584e4, tip: "Red Alert: A Path Beyond"
image safe_get_image("./data/cache/ren.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Command & Conquer: Renegade"
image safe_get_image("./data/cache/tsr.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Tiberian Sun: Reborn"
image safe_get_image("./data/cache/woa.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: "Battle for Dune: War of Assassins"
end
end
# game content container
flow(width: 1.0, fill: true, margin_top: LARGE_PADDING) do
# game info container
stack(width: 340, height: 1.0) do
# logo
image safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/logo.png.package"), width: 1.0, max_height: 124
# web links
stack(width: 1.0, fill: true, padding: 0, padding_top: LARGE_PADDING) do
link "Modifications", text_size: 24
link "Bug Tracker", text_size: 24
link "Player Statistics", text_size: 24
end
# launching ta game
caption "Game Version"
list_box items: [ "Release", "Open Testing" ], width: 1.0, margin_bottom: PADDING
flow(width: 1.0, height: 60) do
button "PLAY", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **CTA_BUTTON_THEME
button safe_get_image("./media/icons/singleplayer.png"), image_height: 1.0, background_nine_slice: NINE_SLICE_SQUARE, **CTA_BUTTON_THEME
button safe_get_image("./media/icons/gear.png"), image_height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_RIGHT, **CTA_BUTTON_THEME
end
inscription "Version: 3.9.2.15", margin_top: PADDING
end
# game events and news container
stack(fill: true, height: 1.0, margin_left: LARGE_PADDING, scroll: true) do
flow(width: 1.0, height: 1.0, max_height: 380, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK) do
image safe_get_image("./media/background.png"), fill: true, aspect_ratio: 16.0 / 9.0
stack(fill: true, height: 1.0, margin_left: PADDING) do
caption "Upcoming Event", color: 0xff_22aa11, font: FONT_BOLD
title "Red Alert: A Path Beyond Game Night", font: FONT_BOLD
tagline "July 11, 2028"
flow(fill: true)
button "Read More", margin_left: PADDING, margin_right: LARGE_PADDING, margin_bottom: PADDING, width: 1.0
end
end
# news container
flow(width: 1.0, margin_top: PADDING) do
9.times do
stack(width: 1.0 / 3, height: 345, aspect_ratio: 1, margin_left: HALF_PADDING, margin_right: HALF_PADDING, margin_bottom: PADDING) do
stack(width: 1.0, fill: true, background_image: safe_get_image("./media/background.png"), background_image_mode: :fill)
stack(width: 1.0, height: 1.0 / 3, padding: PADDING, v_align: :bottom, background_nine_slice: NINE_SLICE_ROUNDED_BOTTOM, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK, border_thickness_top: 1, border_color_top: Gosu::Color::BLACK) do
caption "NEWS", color: 0x88_ffffff, font: FONT_BOLD
tagline "A News Item Post A News Item Post", font: FONT_BOLD
end
end
end
end
end
end
end
end
# battleview/friends container
stack(width: 300, height: 1.0, margin: PADDING, margin_left: 0) do
# self account container
flow(width: 1.0, height: 80) do
# self avatar container
stack(width: 80, height: 1.0, background_image: rounded_avatar(safe_get_image("./media/default.png"))) do
# self online state container
stack(width: 20, height: 20, v_align: :bottom, h_align: :right, background_image: safe_get_image("./media/ui/circle_small.png"), background_image_color: 0xff_26a269)
end
stack(fill: true, height: 1.0, margin_left: HALF_PADDING) do
flow(fill: true)
# self name
caption "moonsense715test", font: FONT_BLACK, text_wrap: :none
# self set online state
link "Online ▼", text_size: 18 do |l|
menu(parent: l) do
menu_item("Online")
menu_item("Do Not Disturb")
menu_item("Away")
menu_item("Invisible")
menu_item("Sign Out")
end.show
end
flow(fill: true)
end
end
# friend management container
flow(width: 1.0, height: 60, margin_top: PADDING) do
flow(width: 1.0, v_align: :center) do
button safe_get_image("./media/icons/singleplayer.png"), image_height: 1.0
button safe_get_image("./media/icons/gear.png"), image_height: 1.0, margin_left: HALF_PADDING
edit_line "", margin_left: HALF_PADDING, fill: true, height: 1.0
end
end
# friends/clanmates list container
stack(width: 1.0, fill: true, margin_top: LARGE_PADDING, scroll: true) do
50.times do |i|
# friend container
flow(width: 1.0, height: 48, padding_top: HALF_PADDING, padding_bottom: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_color: 0, hover: { background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0x44_000000 }) do
# friend avatar container
stack(width: 48, height: 1.0, margin_left: HALF_PADDING, background_image: rounded_avatar(safe_get_image("./media/default.png"))) do
stack(width: 12, height: 12, v_align: :bottom, h_align: :right, background_image: safe_get_image("./media/ui/circle_small.png"), background_image_color: 0xff_26a269)
end
# friend name and status container
stack(fill: true, height: 1.0, margin_left: HALF_PADDING, margin_right: HALF_PADDING) do
stack(v_align: :center) do
caption "moonsense#{715 * (i + 1) % 1000}test", font: FONT_BOLD, text_wrap: :none, text_size: 20
inscription "RA_Under • 13:52", text_wrap: :none, text_size: 14, margin_top: -HALF_PADDING
end
end
# friend active application container
stack(width: 48, height: 1.0, margin_right: HALF_PADDING, background_image: safe_get_image("./media/logo.png"))
end
end
end
end
end
end
end end
end end
end end

View File

@@ -1,7 +1,15 @@
module W3DHubLauncher module W3DHubLauncher
class Worker class Worker
Response = Data.define(:status, :request_id, :data)
def initialize def initialize
@threads = [] @threads = []
@requests = []
@settings = # Settings.new
# next available request_id to assign incoming requests
@request_id = 0
# listen for requests from frontend # listen for requests from frontend
listener = Thread.new { listen } listener = Thread.new { listen }
@@ -10,15 +18,35 @@ module W3DHubLauncher
# connect to and monitor Backend web service # connect to and monitor Backend web service
@threads << Thread.new { backend_websocket } @threads << Thread.new { backend_websocket }
Ractor.main.send({ message: "3 o'clock 'nd all's well!" }) @w3dhub_api = W3DHubLauncher::W3DHubApi.new
listener.join listener.join
end end
def listen def listen
loop do loop do
request = Ractor.receive query = Ractor.receive
pp request pp query
case query.type
when Request::FETCH_URL
when Request::DOWNLOAD_URL
when Request::W3DHUB_API_CALL
Async do
result = @w3dhub_api.send(query.data[:call], *(query.data[:arguments] || []))
response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result)
Ractor.main.send(response)
end
when Request::LAUNCHER_UPDATE_SETTINGS
result = CyberarmEngine::Result.new
FileUtils.mkdir_p(W3DHubLauncher::CONFIG_PATH) # FAILABLE!
File.write("#{W3DHubLauncher::CONFIG_PATH}/settings.json", query.data) # FAILABLE!
result.data = query.data
response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result)
Ractor.main.send(response)
else
raise "UNKNOWN REQUEST"
end
end end
end end

View File

@@ -3,76 +3,71 @@
module W3DHubLauncher module W3DHubLauncher
class Worker class Worker
class Api class Api
Request = Data.define(:coming_soon)
def initialize
@requests = []
end
# downloads requested resource, returns raw string # downloads requested resource, returns raw string
def fetch_url def self.fetch_url
end end
# downloads requested resource, periodically reporting progress until completion, returning path for file on disk # downloads requested resource, periodically reporting progress until completion, returning path for file on disk
def download_url def self.download_url
end end
# returns user account data # returns user account data
# #
# automatically handles signing in / refreshing token (DOES NOT remove account data if failed to refresh token due to network timeout) # automatically handles signing in / refreshing token (DOES NOT remove account data if failed to refresh token due to network timeout)
def account def self.account
end end
# returns launcher settings # returns launcher settings
def settings def self.settings
end end
# write updated launcher settings # write updated launcher settings
def update_settings def self.update_settings(settings, &block)
Worker::Request.new(Request::LAUNCHER_UPDATE_SETTINGS, settings.to_json, &block)
end end
# returns list of available applications # returns list of available applications
# #
# if updated list is requested, return cached version immediately and then the updated list later. # if updated list is requested, return cached version immediately and then the updated list later.
def applications def self.applications
end end
# returns current list of servers as reported from GSH / cache # returns current list of servers as reported from GSH / cache
def servers def self.servers
end end
# returns news for application # returns news for application
def news def self.news
end end
# request installation of application # request installation of application
# #
# periodically reports progress until completion # periodically reports progress until completion
def install_application def self.install_application
end end
# request update of application # request update of application
# #
# periodically reports progress until completion # periodically reports progress until completion
def update_application def self.update_application
end end
# request repair of application # request repair of application
# #
# periodically reports progress until completion # periodically reports progress until completion
def repair_application def self.repair_application
end end
# request relocation of application # request relocation of application
# #
# periodically reports progress until completion # periodically reports progress until completion
def move_application def self.move_application
end end
# request removal of application # request removal of application
# #
# periodically reports progress until completion # periodically reports progress until completion
def uninstall_application def self.uninstall_application
end end
end end
end end

65
lib/worker/request.rb Normal file
View File

@@ -0,0 +1,65 @@
module W3DHubLauncher
class Worker
class Request
Query = Data.define(:type, :request_id, :data)
FETCH_URL = 0
DOWNLOAD_URL = 1
W3DHUB_API_CALL = 10
LAUNCHER_SETTINGS = 1000
LAUNCHER_UPDATE_SETTINGS = 1001
STATUS_ERROR = -1 # request has failed
STATUS_PENDING = 0 # request has not yet started
STATUS_OK = 1 # request completed successfully
STATUS_COMPLETE = STATUS_OK
STATUS_IN_PROGRESS = 2 # request is in progress
STATUS_BUSY = STATUS_IN_PROGRESS
# NOT "Thread"/Ractor safe
@request_id = 0
@requests = []
# NOT "Thread"/Ractor safe. Only call from main ractor
# returns next available request id, and auto increments by 1
def self.request_id
@request_id += 1
end
# NOT "Thread"/Ractor safe.
# returns an array of pending requests
def self.requests
@requests
end
attr_reader :type, :data, :request_id
def initialize(type, data, request_id: Request.request_id, &block)
@type = type.freeze
@data = data.freeze
@status = STATUS_PENDING
@request_id = request_id
@callback = block # only called on error or success
enqueue(@type, @request_id, @data)
end
def enqueue(type, id, data)
Request.requests << self
W3DHubLauncher::WORKER.send(Query.new(type, id, data))
end
# event from Worker received
def handle_event(event, data)
pp [event, data]
case event
when STATUS_ERROR, STATUS_COMPLETE
@callback&.call(data)
Request.requests.delete(self)
end
end
end
end
end

View File

@@ -0,0 +1,109 @@
module W3DHubLauncher
class W3DHubApi
API_TIMEOUT = 30 # seconds
API_CONNECT_TIMEOUT = 10 # seconds
PRIMARY_W3DHUB_API_ENDPOINT = "https://secure.w3dhub.com".freeze
ALTERNATIVE_W3DHUB_API_ENDPOINT = "https://backend.w3d.cyberarm.dev".freeze
def initialize
@access_token = nil
end
def headers(form_encoded: false)
end
# return raw response to requester
def fetch(url, method: :get, body: nil, headers: headers())
result = CyberarmEngine::Result.new
Sync do |task|
task.with_timeout(API_TIMEOUT) do
Async::HTTP::Internet.send(method, url, headers, body) do |response|
result.data = response.read
rescue StandardError => e
result.error = e
end
rescue Async::TimeoutError
result.error = e
end
end
result
end
# write response to file, periodically reporting progress to requester
def download(url, path:, method: :get, body: nil, headers: headers(), &block)
result = CyberarmEngine::Result.new
Sync do |task|
task.with_timeout(API_TIMEOUT) do
Async::HTTP::Internet.send(method, url, headers, body) do |response|
if response.success?
content_length = response.headers["content-length"] || 0
total_downloaded_bytes = 0
File.open(path, "wb") do |file|
response.each do |chunk|
file.write(chunk)
downloaded_bytes = chunk.length
total_downloaded_bytes += downloaded_bytes
block&.call(downloaded_bytes, total_downloaded_bytes, content_length)
end
end
result.data = true
end
rescue StandardError => e
result.error = e
end
rescue Async::TimeoutError
result.error = e
end
end
result
end
def user_login()
result = CyberarmEngine::Result.new
end
def refresh_user_login()
result = CyberarmEngine::Result.new
end
def fetch_user_details()
result = CyberarmEngine::Result.new
end
def fetch_applications
result = CyberarmEngine::Result.new
end
def fetch_news()
result = CyberarmEngine::Result.new
end
def fetch_events()
result = CyberarmEngine::Result.new
end
def fetch_manifest()
result = CyberarmEngine::Result.new
end
def fetch_manifests()
result = CyberarmEngine::Result.new
end
def fetch_package_details()
result = CyberarmEngine::Result.new
end
def fetch_package()
download()
end
end
end

BIN
media/ui/rounded_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

BIN
media/ui/rounded_tiny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

View File

@@ -4,13 +4,44 @@ rescue LoadError
require "cyberarm_engine" require "cyberarm_engine"
end end
require "rexml"
require "base64"
require "logger"
require "async"
require "async/http/internet/instance"
require "async/websocket"
require "digest/crc"
require "ircparser"
require "zip"
require_relative "lib/version" require_relative "lib/version"
require_relative "lib/constants"
require_relative "lib/attribution"
require_relative "lib/gui_ext" require_relative "lib/gui_ext"
require_relative "lib/state"
require_relative "lib/dialog"
require_relative "lib/theme" require_relative "lib/theme"
require_relative "lib/pages/games"
require_relative "lib/pages/server_browser"
require_relative "lib/pages/boot/terms"
require_relative "lib/pages/boot/initial_setup"
require_relative "lib/pages/boot/start_up"
require_relative "lib/dialogs/about"
require_relative "lib/dialogs/downloads"
require_relative "lib/states/boot"
require_relative "lib/states/interface"
require_relative "lib/window" require_relative "lib/window"
require_relative "lib/worker" require_relative "lib/worker"
require_relative "lib/worker/api" require_relative "lib/worker/api"
require_relative "lib/worker/request"
require_relative "lib/worker/w3dhub_api"
require_relative "lib/worker/task"
require_relative "lib/worker/tasks/install_application"
require_relative "lib/worker/tasks/uninstall_application"
require_relative "lib/worker/tasks/repair_application"
require_relative "lib/worker/tasks/update_application"
module W3DHubLauncher module W3DHubLauncher
WORKER = Ractor.new(name: "Parallel Worker") { W3DHubLauncher::Worker.new } WORKER = Ractor.new(name: "Parallel Worker") { W3DHubLauncher::Worker.new }
@@ -25,11 +56,20 @@ end
# NOTE: May need to mangle Window#update to do ruby-land sleep so thread gets time to process :( # NOTE: May need to mangle Window#update to do ruby-land sleep so thread gets time to process :(
Thread.new do Thread.new do
loop do loop do
message = Ractor.receive response = Ractor.receive
pp message pp response
request = W3DHubLauncher::Worker::Request.requests.find { |r| r.request_id == response.request_id }
request&.handle_event(response.status, response.data)
end end
end end
# 10.times do
# W3DHubLauncher::Worker::Request.new(W3DHubLauncher::Worker::Request::W3DHUB_API_CALL, { call: :fetch_applications }) do |result|
# pp result
# end
# end
window = W3DHubLauncher::Window.new(width: 1280, height: 800, resizable: true) window = W3DHubLauncher::Window.new(width: 1280, height: 800, resizable: true)
# window = W3DHubLauncher::Window.new(width: 1920, height: 1080, resizable: true) # window = W3DHubLauncher::Window.new(width: 1920, height: 1080, resizable: true)
window.show window.show