Merge pull request #1741 from ludovic111/feat/styled-menu-titles
🤖 Agent: server dialogs stack brought up to date with main (merge commits); verify passed on this head.
This commit is contained in:
@@ -686,6 +686,18 @@ impl Game<'_> {
|
||||
v["craft_size"] = json!(self.craft_size);
|
||||
v["station"] = json!(self.station.as_ref().map(|s| format!("{:?}", s.kind)));
|
||||
v["container"] = json!(self.container);
|
||||
// The title a server gave the menu, span by span as it is drawn.
|
||||
if let Some(title) = self.remote_menu_title() {
|
||||
let spans = title.spans.iter().map(|s| {
|
||||
json!({
|
||||
"text": s.text,
|
||||
"color": s.style.color.map(|c| format!("#{c:06X}")),
|
||||
"bold": s.style.is_bold(),
|
||||
"italic": s.style.is_italic(),
|
||||
})
|
||||
});
|
||||
v["title"] = json!(spans.collect::<Vec<_>>());
|
||||
}
|
||||
// The recipe book's buttons (GUI pixels): the book button, then
|
||||
// the recipe buttons of the page, page arrows, filter and tabs.
|
||||
let book = self.recipe_book_targets();
|
||||
|
||||
@@ -923,15 +923,16 @@ impl Game<'_> {
|
||||
batch.rect_uv(ox, oy, 176.0, height, r, u32::MAX);
|
||||
}
|
||||
let grey = horizon_client::hud::rgba(64, 64, 64, 255);
|
||||
let title = self
|
||||
.remote_menu_title()
|
||||
.unwrap_or_else(|| self.container_title(self.translate(key, fallback)));
|
||||
let title = &self.remote_menu_title().unwrap_or_else(|| {
|
||||
horizon_client::text::Text::literal(self.container_title(self.translate(key, fallback)))
|
||||
.resolve_legacy()
|
||||
});
|
||||
let title_x = if screen == Screen::Hopper {
|
||||
8.0
|
||||
} else {
|
||||
(176.0 - horizon_client::hud::HudBatch::text_width(atlas, &title)) / 2.0
|
||||
(176.0 - atlas.font.width_text(title)) / 2.0
|
||||
};
|
||||
batch.text(atlas, &title, ox + title_x, oy + 6.0, grey, false);
|
||||
batch.text_styled(atlas, title, ox + title_x, oy + 6.0, grey, false);
|
||||
let inventory = self.translate("container.inventory", "Inventory");
|
||||
batch.text(atlas, &inventory, ox + 8.0, oy + height - 94.0, grey, false);
|
||||
true
|
||||
|
||||
@@ -58,6 +58,12 @@ fn remote_text(tag: &Tag) -> horizon_client::text::Text {
|
||||
comp_text(&tag_to_json(tag))
|
||||
}
|
||||
|
||||
/// `ClientboundOpenScreenPacket.title` as a container screen draws it: the
|
||||
/// component's styles, and the `§` codes a literal holds (`StringDecomposer`).
|
||||
fn menu_title(tag: &Tag) -> horizon_client::text::Text {
|
||||
remote_text(tag).resolve_legacy()
|
||||
}
|
||||
|
||||
/// A section's sky and block light layers from the wire.
|
||||
type LightLayers = (Option<Vec<u8>>, Option<Vec<u8>>);
|
||||
|
||||
@@ -130,8 +136,8 @@ pub(super) struct Remote {
|
||||
predictions: HashMap<[i32; 3], (i32, i32)>,
|
||||
/// Open container: (window id, menu name).
|
||||
pub(super) menu: Option<(i32, String)>,
|
||||
/// The open menu's title as the server named it.
|
||||
pub(super) menu_title: Option<String>,
|
||||
/// The open menu's title as the server named it, with its styles.
|
||||
pub(super) menu_title: Option<horizon_client::text::Text>,
|
||||
state_id: i32,
|
||||
use_target: Option<[i32; 3]>,
|
||||
entities: HashMap<i32, Tracked>,
|
||||
@@ -4793,14 +4799,7 @@ impl Game<'_> {
|
||||
return;
|
||||
}
|
||||
if let Some(kind) = Self::remote_station_kind(&menu) {
|
||||
let title = plain(
|
||||
&p.title,
|
||||
&self
|
||||
.remote
|
||||
.as_ref()
|
||||
.map(|r| r.lang.clone())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
let title = menu_title(&p.title);
|
||||
let pos = target.map_or([0, 0, 0], |t| [t.0, t.1, t.2]);
|
||||
self.remote_open_station(p.container, menu, kind, title, pos);
|
||||
return;
|
||||
@@ -4865,14 +4864,7 @@ impl Game<'_> {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let title = plain(
|
||||
&p.title,
|
||||
&self
|
||||
.remote
|
||||
.as_ref()
|
||||
.map(|r| r.lang.clone())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
let title = menu_title(&p.title);
|
||||
if let Some(r) = self.remote.as_mut() {
|
||||
r.menu = Some((p.container, menu.clone()));
|
||||
r.menu_title = Some(title);
|
||||
@@ -4916,7 +4908,7 @@ impl Game<'_> {
|
||||
Some(self.remote.as_ref()?.menu.as_ref()?.1.as_str())
|
||||
}
|
||||
/// The title the server gave the menu it opened.
|
||||
pub(super) fn remote_menu_title(&self) -> Option<String> {
|
||||
pub(super) fn remote_menu_title(&self) -> Option<horizon_client::text::Text> {
|
||||
self.remote.as_ref()?.menu_title.clone()
|
||||
}
|
||||
/// A language change reaches the server-sent components too: their titles
|
||||
@@ -4929,7 +4921,7 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
/// A server's chest-like menu (`MenuType.GENERIC_9xN`): its rows and title.
|
||||
pub(super) fn remote_chest_menu(&self) -> Option<(usize, String)> {
|
||||
pub(super) fn remote_chest_menu(&self) -> Option<(usize, horizon_client::text::Text)> {
|
||||
let r = self.remote.as_ref()?;
|
||||
let rows = r
|
||||
.menu
|
||||
@@ -4938,7 +4930,12 @@ impl Game<'_> {
|
||||
.strip_prefix("minecraft:generic_9x")?
|
||||
.parse()
|
||||
.ok()?;
|
||||
Some((rows, r.menu_title.clone().unwrap_or_default()))
|
||||
Some((
|
||||
rows,
|
||||
r.menu_title
|
||||
.clone()
|
||||
.unwrap_or_else(horizon_client::text::Text::empty),
|
||||
))
|
||||
}
|
||||
/// ServerboundPlayerCommandPacket STOP_SLEEPING.
|
||||
pub(super) fn remote_stop_sleeping(&mut self) {
|
||||
|
||||
@@ -18,7 +18,7 @@ impl Game<'_> {
|
||||
container: i32,
|
||||
menu: String,
|
||||
kind: Kind,
|
||||
title: String,
|
||||
title: horizon_client::text::Text,
|
||||
pos: [i32; 3],
|
||||
) {
|
||||
if let Some(r) = self.remote.as_mut() {
|
||||
|
||||
@@ -11,6 +11,7 @@ use horizon_client::{
|
||||
hud::{HudBatch, rgba},
|
||||
items::ItemStack,
|
||||
stations::{self, AnvilResult, EnchantOffers},
|
||||
text::Text,
|
||||
};
|
||||
use horizon_core::random::{LegacyRandom, RandomSource};
|
||||
|
||||
@@ -1160,15 +1161,15 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
let default = self.translate("container.brewing", "Brewing Stand");
|
||||
let title = self
|
||||
.station
|
||||
.as_ref()
|
||||
.and_then(|s| self.world.block_names.get(&(s.pos[0], s.pos[1], s.pos[2])))
|
||||
.cloned()
|
||||
.or_else(|| self.remote_menu_title())
|
||||
.unwrap_or(default);
|
||||
let tw = HudBatch::text_width(atlas, &title);
|
||||
batch.text(
|
||||
let title = self.remote_menu_title().unwrap_or_else(|| {
|
||||
let named = self
|
||||
.station
|
||||
.as_ref()
|
||||
.and_then(|s| self.world.block_names.get(&(s.pos[0], s.pos[1], s.pos[2])));
|
||||
Text::literal(named.cloned().unwrap_or(default)).resolve_legacy()
|
||||
});
|
||||
let tw = atlas.font.width_text(&title);
|
||||
batch.text_styled(
|
||||
atlas,
|
||||
&title,
|
||||
ox + (176.0 - tw) / 2.0,
|
||||
@@ -1195,7 +1196,10 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
} else if let Some(kind) = self.station.as_ref().map(|s| s.kind) {
|
||||
let title = self.translate(kind.title_key(), "");
|
||||
// A server's menu has the title it was given.
|
||||
let title = self
|
||||
.remote_menu_title()
|
||||
.unwrap_or_else(|| Text::literal(self.translate(kind.title_key(), "")));
|
||||
{
|
||||
let atlas = &self.gui.atlas;
|
||||
Self::part(
|
||||
@@ -1207,7 +1211,7 @@ impl Game<'_> {
|
||||
(ox, oy),
|
||||
);
|
||||
let (tx, ty) = kind.title_pos();
|
||||
batch.text(atlas, &title, ox + tx, oy + ty, GREY, false);
|
||||
batch.text_styled(atlas, &title, ox + tx, oy + ty, GREY, false);
|
||||
batch.text(atlas, &inventory_label, ox + 8.0, oy + 72.0, GREY, false);
|
||||
}
|
||||
if kind == Kind::Cartography {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! kept in the dropped item's `container` component) and ender chests (the
|
||||
//! player's `PlayerEnderChestContainer`, saved as `EnderItems`).
|
||||
use super::Game;
|
||||
use horizon_client::{containers::BlockEntity, items::ItemStack};
|
||||
use horizon_client::{containers::BlockEntity, items::ItemStack, text::Text};
|
||||
|
||||
pub(super) fn is_shulker_box(name: &str) -> bool {
|
||||
name.ends_with("shulker_box")
|
||||
@@ -233,7 +233,7 @@ impl Game<'_> {
|
||||
generic.then(|| self.container_size() / 9)
|
||||
}
|
||||
/// The title of the open chest-like menu.
|
||||
fn chest_title(&self, rows: usize) -> String {
|
||||
fn chest_title(&self, rows: usize) -> Text {
|
||||
if let Some((_, title)) = self.remote_chest_menu() {
|
||||
return title;
|
||||
}
|
||||
@@ -246,7 +246,7 @@ impl Game<'_> {
|
||||
_ if rows > 3 => ("container.chestDouble", "Large Chest"),
|
||||
_ => ("container.chest", "Chest"),
|
||||
};
|
||||
self.container_title(self.translate(key, fallback))
|
||||
Text::literal(self.container_title(self.translate(key, fallback))).resolve_legacy()
|
||||
}
|
||||
/// `ContainerScreen` / `ShulkerBoxScreen`: the background with its
|
||||
/// container rows, the title and the inventory label.
|
||||
@@ -294,7 +294,7 @@ impl Game<'_> {
|
||||
r.u1 = r.u0 + 176.0 * du;
|
||||
batch.rect_uv(ox, oy + top, 176.0, 96.0, r, u32::MAX);
|
||||
}
|
||||
batch.text(
|
||||
batch.text_styled(
|
||||
atlas,
|
||||
&self.chest_title(rows),
|
||||
ox + 8.0,
|
||||
|
||||
+24
-1
@@ -15,6 +15,8 @@ Checks:
|
||||
payload (the server's "Received custom click action" line), `wait_for_response` leaves the
|
||||
waiting screen, and `/dialog clear` closes a dialog (servers used to see nothing: the game
|
||||
dropped the packets).
|
||||
- titles: a chest named with styles (`CustomName`, as a plugin's menu is): its screen's title keeps
|
||||
the colours and bold (it used to be flattened to plain grey).
|
||||
|
||||
The game runs with fresh HORIZON_HOME, launcher and XDG folders and without audio. Screenshots and both
|
||||
logs go to --out.
|
||||
@@ -137,7 +139,28 @@ def check_dialog(server, game, out):
|
||||
return shown
|
||||
|
||||
|
||||
CHECKS = {'dialog': check_dialog}
|
||||
def check_titles(server, game, out):
|
||||
# AbstractContainerScreen draws the title the server sent, its styles included (#1645). A named
|
||||
# chest is what a plugin's menu looks like: its CustomName is the open_screen title.
|
||||
x, y, z = (int(v // 1) for v in game('state')['player']['position'])
|
||||
chest = (x + 2, y, z)
|
||||
name = [{'text': 'Server ', 'color': 'gold', 'bold': True}, {'text': 'Selector', 'color': 'aqua'}]
|
||||
server.console(f'setblock {chest[0]} {chest[1]} {chest[2]} minecraft:chest{{CustomName:{snbt(name)}}}')
|
||||
game.wait(10)
|
||||
game.raw('interact', *chest)
|
||||
game.raw('wait-for', 'screen', 'container', '--timeout', 30)
|
||||
game.wait(5)
|
||||
title = game('screen').get('title')
|
||||
game.shot(out, 'titles')
|
||||
game.raw('key', 'esc', 'tap')
|
||||
# A list is its first component with the rest as its children: they inherit its bold.
|
||||
want = [{'text': 'Server ', 'color': '#FFAA00', 'bold': True, 'italic': False},
|
||||
{'text': 'Selector', 'color': '#55FFFF', 'bold': True, 'italic': False}]
|
||||
assert title == want, title
|
||||
return {'title': title}
|
||||
|
||||
|
||||
CHECKS = {'dialog': check_dialog, 'titles': check_titles}
|
||||
|
||||
|
||||
def run(bins, out, checks):
|
||||
|
||||
Reference in New Issue
Block a user