Basic structure of character selection

This commit is contained in:
Imbris 2020-06-28 13:46:11 -04:00
parent bc42f3f229
commit de770694d8
24 changed files with 473 additions and 164 deletions

BIN
assets/voxygen/element/frames/gray/corner.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/gray/edge.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/window_4.vox (Stored with Git LFS)

Binary file not shown.

View File

@ -470,10 +470,10 @@ magischen Gegenstände ergattern?"#,
"char_selection.change_server": "Server wechseln.",
"char_selection.enter_world": "Betreten",
"char_selection.logout": "Ausloggen",
"char_selection.create_charater": "Charakter erstellen",
"char_selection.create_character": "Charakter erstellen",
"char_selection.create_new_character": "Neuen Charakter erstellen",
"char_selection.creating_character": "Erstelle Charakter...",
"char_selection.character_creation": "Charaktererstellung",
"char_selection.create_new_charater": "Neuen Charakter erstellen",
"char_selection.human_default": "Human Default",
"char_selection.level_fmt": "Level {level_nb}",

View File

@ -477,7 +477,7 @@ magically infused items?"#,
"char_selection.change_server": "Change Server",
"char_selection.enter_world": "Enter World",
"char_selection.logout": "Logout",
"char_selection.create_new_charater": "Create New Character",
"char_selection.create_new_character": "Create New Character",
"char_selection.creating_character": "Creating Character...",
"char_selection.character_creation": "Character Creation",

View File

@ -358,7 +358,7 @@ objetos imbuidos de magia?"#,
"char_selection.change_server": "Cambiar de servidor",
"char_selection.enter_world": "Entrar al mundo",
"char_selection.logout": "Salir",
"char_selection.create_new_charater": "Crear nuevo personaje",
"char_selection.create_new_character": "Crear nuevo personaje",
"char_selection.creating_character": "Creando personaje...",
"char_selection.character_creation": "Creación de personaje",

View File

@ -372,7 +372,7 @@ objets magiques ?"#,
"char_selection.change_server": "Changer de serveur",
"char_selection.enter_world": "Entrer dans le monde",
"char_selection.logout": "Se déconnecter",
"char_selection.create_new_charater": "Créer un nouveau personnage",
"char_selection.create_new_character": "Créer un nouveau personnage",
"char_selection.creating_character": "Création du personnage...",
"char_selection.character_creation": "Création de personnage",

View File

@ -462,7 +462,7 @@ oggetti infusi di magia?"#,
"char_selection.change_server": "Cambia Server",
"char_selection.enter_world": "Unisciti al Mondo",
"char_selection.logout": "Disconnettiti",
"char_selection.create_new_charater": "Crea un nuovo Personaggio",
"char_selection.create_new_character": "Crea un nuovo Personaggio",
"char_selection.creating_character": "Creazione Personaggio...",
"char_selection.character_creation": "Creazione Personaggio",

View File

@ -343,7 +343,7 @@ Comandos de chat:
"char_selection.change_server": "Mudar de servidor",
"char_selection.enter_world": "Entrar no mundo",
"char_selection.logout": "Desconectar",
"char_selection.create_new_charater": "Criar nova personagem",
"char_selection.create_new_character": "Criar nova personagem",
"char_selection.character_creation": "Criação de personagem",
"char_selection.human_default": "Humano padrão",

View File

@ -400,7 +400,7 @@ https://veloren.net/account/."#,
"char_selection.change_server": "Сменить сервер",
"char_selection.enter_world": "Войти в мир",
"char_selection.logout": "Выйти в меню",
"char_selection.create_new_charater": "Создать нового персонажа",
"char_selection.create_new_character": "Создать нового персонажа",
"char_selection.creating_character": "Создание персонажа...",
"char_selection.character_creation": "Создание персонажа",

View File

@ -37,9 +37,9 @@ impl CharSelectionState {
}
}
fn get_humanoid_body(&self) -> Option<comp::humanoid::Body> {
fn get_humanoid_body(&self, client: &Client) -> Option<comp::humanoid::Body> {
self.char_selection_ui
.selected_character()
.display_character(&client.character_list.characters)
.and_then(|character| match character.body {
comp::Body::Humanoid(body) => Some(body),
_ => None,
@ -92,11 +92,8 @@ impl PlayState for CharSelectionState {
ui::Event::DeleteCharacter(character_id) => {
self.client.borrow_mut().delete_character(character_id);
},
ui::Event::Play(character) => {
// TODO: eliminate option in character id
if let Some(character_id) = character.character.id {
self.client.borrow_mut().request_character(character_id);
}
ui::Event::Play(character_id) => {
self.client.borrow_mut().request_character(character_id);
return PlayStateResult::Switch(Box::new(SessionState::new(
global_state,
@ -106,12 +103,15 @@ impl PlayState for CharSelectionState {
}
}
let humanoid_body = self.get_humanoid_body();
let loadout = self.char_selection_ui.get_loadout();
// Maintain the scene.
{
let client = self.client.borrow();
let humanoid_body = self.get_humanoid_body(&client);
let loadout = self.char_selection_ui.get_loadout();
// Maintain the scene.
let scene_data = scene::SceneData {
time: client.state().get_time(),
delta_time: client.state().ecs().read_resource::<DeltaTime>().0,
@ -127,6 +127,7 @@ impl PlayState for CharSelectionState {
.figure_lod_render_distance
as f32,
};
self.scene.maintain(
global_state.window.renderer_mut(),
scene_data,
@ -185,16 +186,13 @@ impl PlayState for CharSelectionState {
fn name(&self) -> &'static str { "Title" }
fn render(&mut self, renderer: &mut Renderer, _: &Settings) {
let humanoid_body = self.get_humanoid_body();
let client = self.client.borrow();
let humanoid_body = self.get_humanoid_body(&client);
let loadout = self.char_selection_ui.get_loadout();
// Render the scene.
self.scene.render(
renderer,
self.client.borrow().get_tick(),
humanoid_body,
loadout.as_ref(),
);
self.scene
.render(renderer, client.get_tick(), humanoid_body, loadout.as_ref());
// Draw the UI to the screen.
self.char_selection_ui.render(renderer);

View File

@ -4,7 +4,12 @@ use crate::{
ui::{
self,
fonts::IcedFonts as Fonts,
ice::{component::neat_button, style, widget::Overlay, Element, IcedUi as Ui},
ice::{
component::neat_button,
style,
widget::{AspectRatioContainer, Overlay},
Element, IcedUi as Ui,
},
img_ids::{ImageGraphic, VoxelGraphic},
},
window, GlobalState,
@ -13,33 +18,42 @@ use client::Client;
use common::{
assets::Asset,
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
comp,
comp::humanoid,
comp::{self, humanoid},
};
//ImageFrame, Tooltip,
use crate::settings::Settings;
//use std::time::Duration;
//use ui::ice::widget;
use iced::{
button, text_input, Align, Button, Column, Container, HorizontalAlignment, Length, Row, Space,
Text,
button, scrollable, text_input, Align, Button, Column, Container, HorizontalAlignment, Length,
Row, Scrollable, Space, Text,
};
use vek::Rgba;
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
const FILL_FRAC_ONE: f32 = 0.77;
const FILL_FRAC_TWO: f32 = 0.53;
const FILL_FRAC_TWO: f32 = 0.60;
const STARTER_HAMMER: &str = "common.items.weapons.hammer.starter_hammer";
const STARTER_BOW: &str = "common.items.weapons.bow.starter_bow";
const STARTER_AXE: &str = "common.items.weapons.axe.starter_axe";
const STARTER_STAFF: &str = "common.items.weapons.staff.starter_staff";
const STARTER_SWORD: &str = "common.items.weapons.sword.starter_sword";
// TODO: look into what was using this in old ui
const UI_MAIN: iced::Color = iced::Color::from_rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue
image_ids_ice! {
struct Imgs {
<VoxelGraphic>
// TODO: convert large frames into borders
charlist_frame: "voxygen.element.frames.window_4",
server_frame: "voxygen.element.frames.server_frame",
slider_range: "voxygen.element.slider.track",
slider_indicator: "voxygen.element.slider.indicator",
<ImageGraphic>
gray_corner: "voxygen.element.frames.gray.corner",
gray_edge: "voxygen.element.frames.gray.edge",
selection: "voxygen.element.frames.selection",
selection_hover: "voxygen.element.frames.selection_hover",
selection_press: "voxygen.element.frames.selection_press",
@ -48,7 +62,6 @@ image_ids_ice! {
delete_button_hover: "voxygen.element.buttons.x_red_hover",
delete_button_press: "voxygen.element.buttons.x_red_press",
name_input: "voxygen.element.misc_bg.textbox",
// Tool Icons
@ -80,7 +93,6 @@ image_ids_ice! {
icon_border_press: "voxygen.element.buttons.border_press",
icon_border_pressed: "voxygen.element.buttons.border_pressed",
<ImageGraphic>
button: "voxygen.element.buttons.button",
button_hover: "voxygen.element.buttons.button_hover",
button_press: "voxygen.element.buttons.button_press",
@ -100,7 +112,7 @@ image_ids_ice! {
pub enum Event {
Logout,
Play(CharacterItem),
Play(CharacterId),
AddCharacter {
alias: String,
tool: Option<String>,
@ -109,17 +121,14 @@ pub enum Event {
DeleteCharacter(CharacterId),
}
struct CharacterList {
characters: Vec<CharacterItem>,
selected_character: usize,
}
enum Mode {
Select {
list: Option<CharacterList>,
// Index of selected character
selected: Option<usize>,
characters_scroll: scrollable::State,
character_buttons: Vec<button::State>,
new_character_button: button::State,
logout_button: button::State,
enter_world_button: button::State,
change_server_button: button::State,
@ -136,6 +145,33 @@ enum Mode {
},
}
impl Mode {
pub fn select() -> Self {
Self::Select {
selected: None,
characters_scroll: Default::default(),
character_buttons: Vec::new(),
new_character_button: Default::default(),
logout_button: Default::default(),
enter_world_button: Default::default(),
change_server_button: Default::default(),
}
}
pub fn create(name: String) -> Self {
Self::Create {
name,
body: humanoid::Body::random(),
loadout: comp::Loadout::default(),
tool: Some(STARTER_SWORD),
name_input: Default::default(),
back_button: Default::default(),
create_button: Default::default(),
}
}
}
#[derive(PartialEq)]
enum InfoContent {
Deletion(usize),
@ -202,22 +238,16 @@ impl Controls {
alpha,
info_content: None,
mode: Mode::Select {
list: None,
character_buttons: Vec::new(),
new_character_button: Default::default(),
logout_button: Default::default(),
enter_world_button: Default::default(),
change_server_button: Default::default(),
},
mode: Mode::select(),
}
}
fn view(&mut self, settings: &Settings) -> Element<Message> {
fn view(&mut self, settings: &Settings, client: &Client) -> Element<Message> {
// TODO: if enter key pressed and character is selected then enter the world
// TODO: tooltip widget
let imgs = &self.imgs;
let fonts = &self.fonts;
let i18n = &self.i18n;
let button_style = style::button::Style::new(imgs.button)
@ -232,7 +262,7 @@ impl Controls {
.horizontal_alignment(HorizontalAlignment::Right);
let alpha = iced::Text::new(&self.alpha)
.size(self.fonts.cyri.scale(15))
.size(self.fonts.cyri.scale(12))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center);
@ -245,7 +275,8 @@ impl Controls {
let content = match &mut self.mode {
Mode::Select {
list,
selected,
ref mut characters_scroll,
ref mut character_buttons,
ref mut new_character_button,
ref mut logout_button,
@ -253,14 +284,41 @@ impl Controls {
ref mut change_server_button,
} => {
// TODO: impl delete prompt as overlay
let change_server = Space::new(Length::Units(100), Length::Units(40));
let characters = if let Some(list) = list {
let num = list.characters.len();
let server = Container::new(
Column::with_children(vec![
Text::new(&client.server_info.name)
.size(fonts.cyri.scale(25))
//.horizontal_alignment(HorizontalAlignment::Center)
.into(),
Container::new(neat_button(
change_server_button,
i18n.get("char_selection.change_server"),
FILL_FRAC_TWO,
button_style,
Some(Message::ChangeServer),
))
.height(Length::Units(35))
.into(),
])
.spacing(5)
.align_items(Align::Center),
)
.style(style::container::Style::color_with_image_border(
Rgba::new(0, 0, 0, 217),
imgs.gray_corner,
imgs.gray_edge,
))
.padding(12)
.center_x()
.width(Length::Fill);
let characters = {
let characters = &client.character_list.characters;
let num = characters.len();
// Ensure we have enough button states
character_buttons.resize_with(num * 2, Default::default);
let mut characters = list
.characters
let mut characters = characters
.iter()
.zip(character_buttons.chunks_exact_mut(2))
.map(|(character, buttons)| {
@ -273,65 +331,102 @@ impl Controls {
.enumerate()
.map(|(i, (character, (select_button, delete_button)))| {
Overlay::new(
Button::new(select_button, Space::new(Length::Fill, Length::Fill))
.width(Length::Units(20))
.height(Length::Units(20))
.style(
style::button::Style::new(imgs.delete_button)
.hover_image(imgs.delete_button_hover)
.press_image(imgs.delete_button_press),
),
Button::new(
delete_button,
Column::with_children(vec![
Text::new("Hi").into(),
Text::new("Hi").into(),
Text::new("Hi").into(),
]),
select_button,
Space::new(Length::Units(20), Length::Units(20)),
)
.style(
style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover)
.press_image(imgs.selection_press),
style::button::Style::new(imgs.delete_button)
.hover_image(imgs.delete_button_hover)
.press_image(imgs.delete_button_press),
),
AspectRatioContainer::new(
Button::new(
delete_button,
Column::with_children(vec![
Text::new("Hi").width(Length::Fill).into(),
Text::new("Hi").into(),
Text::new("Hi").into(),
]),
)
.style(
style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover)
.press_image(imgs.selection_press),
),
)
.ratio_of_image(imgs.selection),
)
.padding(15)
.align_x(Align::End)
.into()
})
.collect::<Vec<_>>();
// Add create new character button
let color = if num >= MAX_CHARACTERS_PER_PLAYER {
iced::Color::from_rgb8(97, 97, 25)
(97, 97, 25)
} else {
iced::Color::from_rgb8(97, 255, 18)
(97, 255, 18)
};
characters.push(
Button::new(
new_character_button,
Text::new(i18n.get("char_selection.create_new_character")),
)
.style(
style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover)
.press_image(imgs.selection_press)
.image_color(color)
.text_color(color),
)
AspectRatioContainer::new({
let button = Button::new(
new_character_button,
Container::new(Text::new(
i18n.get("char_selection.create_new_character"),
))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y(),
)
.style(
style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover)
.press_image(imgs.selection_press)
.image_color(Rgba::new(color.0, color.1, color.2, 255))
.text_color(iced::Color::from_rgb8(color.0, color.1, color.2))
.disabled_text_color(iced::Color::from_rgb8(
color.0, color.1, color.2,
)),
)
.width(Length::Fill)
.height(Length::Fill);
// TODO: try to get better interface for this in iced
if num < MAX_CHARACTERS_PER_PLAYER {
button.on_press(Message::NewCharacter)
} else {
button
}
})
.ratio_of_image(imgs.selection)
.into(),
);
characters
} else {
Vec::new()
};
let characters = Column::with_children(characters);
// TODO: could replace column with scrollable completely if it had a with
// children method
let characters = Container::new(
Scrollable::new(characters_scroll)
.push(Column::with_children(characters).spacing(2)),
)
.style(style::container::Style::color_with_image_border(
Rgba::new(0, 0, 0, 217),
imgs.gray_corner,
imgs.gray_edge,
))
.padding(9)
.width(Length::Fill)
.height(Length::Fill);
let right_column =
Column::with_children(vec![change_server.into(), characters.into()])
.spacing(10)
.width(Length::Fill)
.height(Length::Fill)
.max_width(300);
let right_column = Column::with_children(vec![server.into(), characters.into()])
.padding(15)
.spacing(10)
.width(Length::Fill)
.height(Length::Fill)
.max_width(360);
let top = Container::new(right_column)
.width(Length::Fill)
@ -350,23 +445,22 @@ impl Controls {
i18n.get("char_selection.enter_world"),
FILL_FRAC_ONE,
button_style,
Some(Message::EnterWorld),
selected.map(|_| Message::EnterWorld),
);
let bottom = Row::with_children(vec![
Container::new(logout)
.width(Length::Fill)
.height(Length::Units(40))
.align_y(Align::End)
.into(),
Container::new(enter_world)
.width(Length::Fill)
.height(Length::Units(60))
.center_x()
.align_y(Align::End)
.into(),
Space::new(Length::Fill, Length::Shrink).into(),
]);
])
.align_items(Align::End);
Column::with_children(vec![top.into(), bottom.into()])
.width(Length::Fill)
@ -400,13 +494,72 @@ impl Controls {
.into()
}
fn update(&mut self, message: Message, events: &mut Vec<Event>, settings: &Settings) {
fn update(
&mut self,
message: Message,
events: &mut Vec<Event>,
settings: &Settings,
characters: &[CharacterItem],
) {
let servers = &settings.networking.servers;
//match message { }
match message {
Message::Back => {
if matches!(&self.mode, Mode::Create { .. }) {
self.mode = Mode::select();
}
},
Message::Logout => {
events.push(Event::Logout);
},
Message::EnterWorld => {
if let Mode::Select {
selected: Some(selected),
..
} = &self.mode
{
// TODO: eliminate option in character id
if let Some(id) = characters.get(*selected).and_then(|i| i.character.id) {
events.push(Event::Play(id));
}
}
},
Message::Delete(idx) => {
if let Some(id) = characters.get(idx).and_then(|i| i.character.id) {
events.push(Event::DeleteCharacter(id));
}
},
Message::ChangeServer => {
events.push(Event::Logout);
},
Message::NewCharacter => {
if matches!(&self.mode, Mode::Select { .. }) {
self.mode = Mode::create(String::new());
}
},
Message::CreateCharacter => {
if let Mode::Create {
name, body, tool, ..
} = &self.mode
{
events.push(Event::AddCharacter {
alias: name.clone(),
tool: tool.map(String::from),
body: comp::Body::Humanoid(*body),
});
self.mode = Mode::select();
}
},
Message::Name(value) => {
if let Mode::Create { name, .. } = &mut self.mode {
*name = value;
}
},
}
}
pub fn selected_character(&self) -> Option<&CharacterItem> {
/// Get the character to display
pub fn display_character(&self, characters: &[CharacterItem]) -> Option<&CharacterItem> {
match self.mode {
// TODO
Mode::Select { .. } => None,
@ -454,12 +607,13 @@ impl CharSelectionUi {
Self { ui, controls }
}
pub fn selected_character(&self) -> Option<&CharacterItem> {
self.controls.selected_character()
pub fn display_character(&self, characters: &[CharacterItem]) -> Option<&CharacterItem> {
self.controls.display_character(characters)
}
// TODO
pub fn get_loadout(&mut self) -> Option<comp::Loadout> {
// TODO: error gracefully
// TODO: don't clone
/*match &mut self.mode {
Mode::Select(character_list) => {
@ -471,20 +625,20 @@ impl CharSelectionUi {
},
Mode::Create { loadout, tool, .. } => {
loadout.active_item = tool.map(|tool| comp::ItemConfig {
item: (*load_expect::<comp::Item>(tool)).clone(),
item: comp::Item::new_from_asset_expect(tool),
ability1: None,
ability2: None,
ability3: None,
block_ability: None,
dodge_ability: None,
});
loadout.chest = Some(assets::load_expect_cloned(
loadout.chest = Some(comp::Item::new_from_asset_expect(
"common.items.armor.starter.rugged_chest",
));
loadout.pants = Some(assets::load_expect_cloned(
loadout.pants = Some(comp::Item::new_from_asset_expect(
"common.items.armor.starter.rugged_pants",
));
loadout.foot = Some(assets::load_expect_cloned(
loadout.foot = Some(comp::Item::new_from_asset_expect(
"common.items.armor.starter.sandals_0",
));
Some(loadout.clone())
@ -508,17 +662,22 @@ impl CharSelectionUi {
}
}
// TODO: do we need whole client here or just character list
pub fn maintain(&mut self, global_state: &mut GlobalState, client: &mut Client) -> Vec<Event> {
let mut events = Vec::new();
let (messages, _) = self.ui.maintain(
self.controls.view(&global_state.settings),
self.controls.view(&global_state.settings, &client),
global_state.window.renderer_mut(),
);
messages.into_iter().for_each(|message| {
self.controls
.update(message, &mut events, &global_state.settings)
self.controls.update(
message,
&mut events,
&global_state.settings,
&client.character_list.characters,
)
});
events

View File

@ -98,11 +98,13 @@ impl Screen {
.height(Length::Fill);
let prompt_window = Container::new(content)
.style(style::container::Style::color_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
))
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.padding(20);
let container = Container::new(prompt_window)

View File

@ -64,11 +64,13 @@ impl Screen {
.width(Length::Fill)
.height(Length::Fill),
)
.style(style::container::Style::color_double_cornerless_border(
(22, 19, 17, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
)),
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 19, 17, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
),
)
.center_x()
.center_y()

View File

@ -95,7 +95,7 @@ impl Screen {
// Note: a way to tell it to keep the height of this one piece constant and
// unstreched would be nice, I suppose we could just break this out into a
// column and use Length::Units
Graphic::gradient(Rgba::new(0, 0, 0, 240), Rgba::zero(), [500, 30], [0, 300]),
Graphic::gradient(Rgba::new(0, 0, 0, 240), Rgba::zero(), [500, 50], [0, 300]),
])
.height(Length::Shrink),
Text::new(intro_text).size(fonts.cyri.scale(21)),
@ -128,11 +128,13 @@ impl Screen {
.height(Length::Fill)
.width(Length::Fill),
)
.style(style::container::Style::color_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
))
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.width(Length::Units(400))
.height(Length::Units(180))
.padding(20)

View File

@ -239,7 +239,7 @@ impl Controls {
.horizontal_alignment(HorizontalAlignment::Right);
let alpha = iced::Text::new(&self.alpha)
.size(self.fonts.cyri.scale(15))
.size(self.fonts.cyri.scale(12))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center);

View File

@ -81,11 +81,13 @@ impl Screen {
.spacing(10)
.padding(20),
)
.style(style::container::Style::color_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
))
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.max_width(500),
)
.width(Length::Fill)

View File

@ -24,7 +24,7 @@ pub fn neat_button<M: Clone + 'static>(
let container = AspectRatioContainer::new(button);
let container = match button_style.active().0 {
Some(img) => container.ratio_of_image(img),
Some((img, _)) => container.ratio_of_image(img),
None => container,
};

View File

@ -1,12 +1,13 @@
use super::super::super::widget::image;
use iced::Color;
use vek::Rgba;
#[derive(Clone, Copy)]
struct Background {
default: image::Handle,
hover: image::Handle,
press: image::Handle,
color: Color,
color: Rgba<u8>,
}
impl Background {
@ -15,7 +16,7 @@ impl Background {
default: image,
hover: image,
press: image,
color: Color::WHITE,
color: Rgba::white(),
}
}
}
@ -62,7 +63,7 @@ impl Style {
// TODO: this needs to be refactored since the color isn't used if there is no
// background
pub fn image_color(mut self, color: Color) -> Self {
pub fn image_color(mut self, color: Rgba<u8>) -> Self {
if let Some(background) = &mut self.background {
background.color = color;
}
@ -79,24 +80,30 @@ impl Style {
self
}
pub fn disabled(&self) -> (Option<image::Handle>, Color) {
pub fn disabled(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| b.default),
self.background.as_ref().map(|b| (b.default, b.color)),
self.disabled_text,
)
}
pub fn pressed(&self) -> (Option<image::Handle>, Color) {
(self.background.as_ref().map(|b| b.press), self.enabled_text)
}
pub fn hovered(&self) -> (Option<image::Handle>, Color) {
(self.background.as_ref().map(|b| b.hover), self.enabled_text)
}
pub fn active(&self) -> (Option<image::Handle>, Color) {
pub fn pressed(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| b.default),
self.background.as_ref().map(|b| (b.press, b.color)),
self.enabled_text,
)
}
pub fn hovered(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| (b.hover, b.color)),
self.enabled_text,
)
}
pub fn active(&self) -> (Option<(image::Handle, Rgba<u8>)>, Color) {
(
self.background.as_ref().map(|b| (b.default, b.color)),
self.enabled_text,
)
}

View File

@ -3,7 +3,14 @@ use vek::Rgba;
/// Container Border
pub enum Border {
DoubleCornerless { inner: Rgba<u8>, outer: Rgba<u8> },
DoubleCornerless {
inner: Rgba<u8>,
outer: Rgba<u8>,
},
Image {
corner: image::Handle,
edge: image::Handle,
},
None,
}
@ -22,13 +29,23 @@ impl Style {
pub fn color(color: Rgba<u8>) -> Self { Self::Color(color, Border::None) }
/// Shorthand for a color background with a cornerless border
pub fn color_double_cornerless_border(
pub fn color_with_double_cornerless_border(
color: Rgba<u8>,
inner: Rgba<u8>,
outer: Rgba<u8>,
) -> Self {
Self::Color(color, Border::DoubleCornerless { inner, outer })
}
/// Shorthand for a color background with image borders where the corners
/// are inset
pub fn color_with_image_border(
color: Rgba<u8>,
corner: image::Handle,
edge: image::Handle,
) -> Self {
Self::Color(color, Border::Image { corner, edge })
}
}
impl Default for Style {

View File

@ -1,6 +1,5 @@
use super::super::{super::Rotation, style, Defaults, IcedRenderer, Primitive};
use iced::{button, mouse, Element, Layout, Point, Rectangle};
use vek::Rgba;
impl button::Renderer for IcedRenderer {
// TODO: what if this gets large enough to not be copied around?
@ -40,11 +39,11 @@ impl button::Renderer for IcedRenderer {
cursor_position,
);
let primitive = if let Some(handle) = maybe_image {
let primitive = if let Some((handle, color)) = maybe_image {
let background = Primitive::Image {
handle: (handle, Rotation::None),
bounds,
color: Rgba::broadcast(255),
color,
};
Primitive::Group {

View File

@ -2,7 +2,9 @@ use super::super::{super::Rotation, style, IcedRenderer, Primitive};
use common::util::srgba_to_linear;
use iced::{container, Element, Layout, Point, Rectangle};
use style::container::Border;
use vek::Rgba;
// TODO: move to style
const BORDER_SIZE: u16 = 8;
impl container::Renderer for IcedRenderer {
@ -148,6 +150,125 @@ impl container::Renderer for IcedRenderer {
content,
]
},
Border::Image { corner, edge } => {
let border_size = f32::from(BORDER_SIZE)
.min(bounds.width / 4.0)
.min(bounds.height / 4.0);
let center = Primitive::Rectangle {
bounds: Rectangle {
x: bounds.x + border_size,
y: bounds.y + border_size,
width: bounds.width - border_size * 2.0,
height: bounds.height - border_size * 2.0,
},
linear_color,
};
let color = Rgba::white();
let tl_corner = Primitive::Image {
handle: (*corner, Rotation::None),
bounds: Rectangle {
x: bounds.x,
y: bounds.y,
width: border_size,
height: border_size,
},
color,
};
let tr_corner = Primitive::Image {
handle: (*corner, Rotation::Cw90),
bounds: Rectangle {
x: bounds.x + bounds.width - border_size,
y: bounds.y,
width: border_size,
height: border_size,
},
color,
};
let bl_corner = Primitive::Image {
handle: (*corner, Rotation::Cw270),
bounds: Rectangle {
x: bounds.x,
y: bounds.y + bounds.height - border_size,
width: border_size,
height: border_size,
},
color,
};
let br_corner = Primitive::Image {
handle: (*corner, Rotation::Cw180),
bounds: Rectangle {
x: bounds.x + bounds.width - border_size,
y: bounds.y + bounds.height - border_size,
width: border_size,
height: border_size,
},
color,
};
let top_edge = Primitive::Image {
handle: (*edge, Rotation::None),
bounds: Rectangle {
x: bounds.x + border_size,
y: bounds.y,
width: bounds.width - 2.0 * border_size,
height: border_size,
},
color,
};
let bottom_edge = Primitive::Image {
handle: (*edge, Rotation::Cw180),
bounds: Rectangle {
x: bounds.x + border_size,
y: bounds.y + bounds.height - border_size,
width: bounds.width - 2.0 * border_size,
height: border_size,
},
color,
};
let left_edge = Primitive::Image {
handle: (*edge, Rotation::Cw270),
bounds: Rectangle {
x: bounds.x,
y: bounds.y + border_size,
width: border_size,
height: bounds.height - 2.0 * border_size,
},
color,
};
let right_edge = Primitive::Image {
handle: (*edge, Rotation::Cw90),
bounds: Rectangle {
x: bounds.x + bounds.width - border_size,
y: bounds.y + border_size,
width: border_size,
height: bounds.height - 2.0 * border_size,
},
color,
};
// Is this worth it as opposed to using a giant image? (Probably)
vec![
center,
tl_corner,
tr_corner,
bl_corner,
br_corner,
top_edge,
bottom_edge,
left_edge,
right_edge,
content,
]
},
};
Primitive::Group { primitives }

View File

@ -112,12 +112,12 @@ where
let under_size = under.size();
let limits = limits.pad(padding);
let mut over = self.under.layout(renderer, &limits.loose());
let mut over = self.over.layout(renderer, &limits.loose());
let over_size = over.size();
let size = limits.resolve(Size {
width: under_size.width.max(over_size.width),
height: under_size.width.max(over_size.width),
height: under_size.height.max(over_size.height),
});
over.move_to(Point::new(padding, padding));