Add campaign editor script runtime and debug overlays
This commit is contained in:
1 parent
cd35ca8e3d
commit
b06c48172b
13 files changed
+2211
-66
No files matched your search
+231
-8
@@ -3,6 +3,7 @@
|
||||
// Drawn as CP437 windows on top of the (paused) game world.
|
||||
|
||||
use crate::campaign::{missions, Progress};
|
||||
use crate::campaign_doc::CampaignDoc;
|
||||
use crate::cloud::{date_string, duration_string, Cloud};
|
||||
use crate::game::Goal;
|
||||
use crate::gfx::*;
|
||||
@@ -24,6 +25,7 @@ pub enum Page {
|
||||
KeyCapture { action: usize, from_title: bool },
|
||||
Saves { save: bool, from_title: bool }, // savegame list on the server (save or load mode)
|
||||
Campaign, // list of the campaign missions
|
||||
CampaignEditor, // editable shareable .dccamp campaign file
|
||||
Briefing { mission: usize, ingame: bool }, // story and goals of a mission
|
||||
Login, // login to the savegame service (before the title)
|
||||
Account, // password, logout, users (admin)
|
||||
@@ -31,7 +33,7 @@ pub enum Page {
|
||||
Bestiary { tab: usize, sel: usize, level: u32, from_title: bool }, // units, buildings, ranks
|
||||
}
|
||||
|
||||
pub const TABS: [&str; 5] = ["set.tab.sound", "set.tab.camera", "set.tab.graphics", "set.tab.game", "set.tab.keys"];
|
||||
pub const TABS: [&str; 6] = ["set.tab.sound", "set.tab.camera", "set.tab.graphics", "set.tab.game", "set.tab.debug", "set.tab.keys"];
|
||||
|
||||
// One adjustable value in the settings dialog
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
@@ -51,6 +53,11 @@ pub enum Item {
|
||||
UiScale,
|
||||
RenderScale,
|
||||
DebugFps,
|
||||
DebugFrameTime,
|
||||
DebugPing,
|
||||
DebugTriggerZones,
|
||||
DebugCommandMarkers,
|
||||
DebugPathLines,
|
||||
GameSpeed,
|
||||
Hints,
|
||||
TouchUi,
|
||||
@@ -63,7 +70,8 @@ fn tab_items(tab: usize) -> &'static [Item] {
|
||||
0 => &[Language, Volume, MusicVolume, Mute, Voice],
|
||||
1 => &[RotateSpeed, PanSpeed, ZoomSpeed, EdgeScroll, InvertMouse],
|
||||
2 => &[Outlines, WallDetail, Lights, UiScale, RenderScale],
|
||||
3 => &[GameSpeed, Hints, TouchUi, DebugFps],
|
||||
3 => &[GameSpeed, Hints, TouchUi],
|
||||
4 => &[DebugFps, DebugFrameTime, DebugPing, DebugTriggerZones, DebugCommandMarkers, DebugPathLines],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
@@ -85,6 +93,11 @@ fn item_label(it: Item) -> String {
|
||||
Item::UiScale => "set.ui_scale",
|
||||
Item::RenderScale => "set.render_scale",
|
||||
Item::DebugFps => "set.debug_fps",
|
||||
Item::DebugFrameTime => "set.debug_frame_time",
|
||||
Item::DebugPing => "set.debug_ping",
|
||||
Item::DebugTriggerZones => "set.debug_trigger_zones",
|
||||
Item::DebugCommandMarkers => "set.debug_command_markers",
|
||||
Item::DebugPathLines => "set.debug_path_lines",
|
||||
Item::GameSpeed => "set.game_speed",
|
||||
Item::Hints => "set.hints",
|
||||
Item::TouchUi => "set.touch_ui",
|
||||
@@ -127,6 +140,11 @@ fn item_value(it: Item, s: &Settings) -> String {
|
||||
}
|
||||
}
|
||||
Item::DebugFps => onoff(s.debug_fps),
|
||||
Item::DebugFrameTime => onoff(s.debug_frame_time),
|
||||
Item::DebugPing => onoff(s.debug_ping),
|
||||
Item::DebugTriggerZones => onoff(s.debug_trigger_zones),
|
||||
Item::DebugCommandMarkers => onoff(s.debug_command_markers),
|
||||
Item::DebugPathLines => onoff(s.debug_path_lines),
|
||||
Item::RenderScale => {
|
||||
// the setting is for a 1080 pixel high window; show what applies to this one
|
||||
let now = crate::render3d::effective_div(s.render_scale.max(1) as u32, crate::ui::sh());
|
||||
@@ -178,6 +196,11 @@ fn item_change(it: Item, s: &mut Settings, d: i32) {
|
||||
Item::UiScale => s.ui_factor = cycle(&[0u8, 1, 2, 3, 4], s.ui_factor, d),
|
||||
Item::RenderScale => s.render_scale = cycle(&(1u8..=16).collect::<Vec<u8>>(), s.render_scale.clamp(1, 16), d),
|
||||
Item::DebugFps => s.debug_fps = !s.debug_fps,
|
||||
Item::DebugFrameTime => s.debug_frame_time = !s.debug_frame_time,
|
||||
Item::DebugPing => s.debug_ping = !s.debug_ping,
|
||||
Item::DebugTriggerZones => s.debug_trigger_zones = !s.debug_trigger_zones,
|
||||
Item::DebugCommandMarkers => s.debug_command_markers = !s.debug_command_markers,
|
||||
Item::DebugPathLines => s.debug_path_lines = !s.debug_path_lines,
|
||||
Item::GameSpeed => s.game_speed = cycle(&[1.0f32, 2.0], s.game_speed, d),
|
||||
Item::Hints => s.hints = !s.hints,
|
||||
Item::TouchUi => s.touch_ui = cycle(&[0u8, 1, 2], s.touch_ui, d),
|
||||
@@ -218,6 +241,20 @@ enum Btn {
|
||||
FileLoad,
|
||||
NameField,
|
||||
Campaign,
|
||||
CampaignEditor,
|
||||
CampaignNew,
|
||||
CampaignOpen,
|
||||
CampaignSave,
|
||||
CampaignAdd,
|
||||
CampaignDelete,
|
||||
CampaignEditName,
|
||||
CampaignEditTitle,
|
||||
CampaignEditGoals,
|
||||
CampaignEditNext,
|
||||
CampaignEditBranches,
|
||||
CampaignEditScript,
|
||||
CampaignStart,
|
||||
CampaignMission(usize),
|
||||
Mission(usize),
|
||||
StartMission,
|
||||
Goals,
|
||||
@@ -302,6 +339,18 @@ pub enum MenuAction {
|
||||
CloudDelete(String),
|
||||
EditName, // the name field was tapped (touch: ask with a browser dialog)
|
||||
StartMission(usize),
|
||||
CampaignNew,
|
||||
CampaignOpen,
|
||||
CampaignSave,
|
||||
CampaignAdd,
|
||||
CampaignDelete(usize),
|
||||
CampaignEditName,
|
||||
CampaignEditTitle,
|
||||
CampaignEditGoals,
|
||||
CampaignEditNext,
|
||||
CampaignEditBranches,
|
||||
CampaignEditScript,
|
||||
CampaignStart(usize),
|
||||
OpenRepo,
|
||||
OpenClaudeCode,
|
||||
Login(String, String),
|
||||
@@ -339,6 +388,9 @@ pub struct Menu {
|
||||
pub match_ui: MatchUi, // copied state of the multiplayer relay, shown on the Match page
|
||||
pub coop_mission: usize, // co-op campaign mission selected on the Match page
|
||||
pub in_match: bool, // a match is being played; the pause menu changes its shape
|
||||
pub campaign_doc: CampaignDoc,
|
||||
pub campaign_selected: usize,
|
||||
pub campaign_pending: bool,
|
||||
confirm: Option<Btn>, // overwrite / delete waiting for a second click
|
||||
scroll: usize,
|
||||
rows_visible: usize,
|
||||
@@ -366,6 +418,9 @@ impl Menu {
|
||||
match_ui: MatchUi::default(),
|
||||
coop_mission: crate::campaign::coop_mission_indexes().first().copied().unwrap_or(0),
|
||||
in_match: false,
|
||||
campaign_doc: CampaignDoc::default(),
|
||||
campaign_selected: 0,
|
||||
campaign_pending: false,
|
||||
confirm: None,
|
||||
scroll: 0,
|
||||
rows_visible: 1,
|
||||
@@ -380,13 +435,14 @@ impl Menu {
|
||||
// Esc: go one level back
|
||||
pub fn back(&mut self) {
|
||||
self.page = match self.page {
|
||||
Some(Page::KeyCapture { from_title, .. }) => Some(Page::Settings { tab: 4, from_title }),
|
||||
Some(Page::KeyCapture { from_title, .. }) => Some(Page::Settings { tab: 5, from_title }),
|
||||
Some(Page::Settings { from_title: true, .. }) => Some(Page::Title),
|
||||
Some(Page::Settings { from_title: false, .. }) => Some(Page::Pause),
|
||||
Some(Page::NewGame) => Some(Page::Title),
|
||||
Some(Page::Saves { from_title: true, .. }) => Some(Page::Title),
|
||||
Some(Page::Saves { from_title: false, .. }) => Some(Page::Pause),
|
||||
Some(Page::Campaign) => Some(Page::Title),
|
||||
Some(Page::CampaignEditor) => Some(Page::Title),
|
||||
Some(Page::Login) => Some(Page::Login),
|
||||
Some(Page::Account) => Some(Page::Title),
|
||||
Some(Page::Match { from_title }) => Some(if from_title { Page::Title } else { Page::Pause }),
|
||||
@@ -488,7 +544,7 @@ impl Menu {
|
||||
|
||||
pub fn finish_capture(&mut self) {
|
||||
if let Some(Page::KeyCapture { from_title, .. }) = self.page {
|
||||
self.page = Some(Page::Settings { tab: 4, from_title });
|
||||
self.page = Some(Page::Settings { tab: 5, from_title });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,6 +725,36 @@ impl Menu {
|
||||
self.scroll = 0;
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::CampaignEditor) => {
|
||||
self.page = Some(Page::CampaignEditor);
|
||||
self.scroll = 0;
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::CampaignNew) => MenuAction::CampaignNew,
|
||||
Some(Btn::CampaignOpen) => MenuAction::CampaignOpen,
|
||||
Some(Btn::CampaignSave) => MenuAction::CampaignSave,
|
||||
Some(Btn::CampaignAdd) => MenuAction::CampaignAdd,
|
||||
Some(Btn::CampaignEditName) => MenuAction::CampaignEditName,
|
||||
Some(Btn::CampaignEditTitle) => MenuAction::CampaignEditTitle,
|
||||
Some(Btn::CampaignEditGoals) => MenuAction::CampaignEditGoals,
|
||||
Some(Btn::CampaignEditNext) => MenuAction::CampaignEditNext,
|
||||
Some(Btn::CampaignEditBranches) => MenuAction::CampaignEditBranches,
|
||||
Some(Btn::CampaignEditScript) => MenuAction::CampaignEditScript,
|
||||
Some(b @ Btn::CampaignDelete) => {
|
||||
let i = self.campaign_selected;
|
||||
if confirm == Some(b) {
|
||||
MenuAction::CampaignDelete(i)
|
||||
} else {
|
||||
self.confirm = Some(b);
|
||||
MenuAction::Consumed
|
||||
}
|
||||
}
|
||||
Some(Btn::CampaignStart) => MenuAction::CampaignStart(self.campaign_selected),
|
||||
Some(Btn::CampaignMission(i)) => {
|
||||
self.campaign_selected = i;
|
||||
self.scroll = 0;
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::Mission(i)) => {
|
||||
self.page = Some(Page::Briefing { mission: i, ingame: false });
|
||||
self.scroll = 0;
|
||||
@@ -711,13 +797,14 @@ impl Menu {
|
||||
Page::Settings { tab, .. } => self.draw_settings(gfx, settings, tab),
|
||||
Page::Saves { save, .. } => self.draw_saves(gfx, cloud, save),
|
||||
Page::Campaign => self.draw_campaign(gfx),
|
||||
Page::CampaignEditor => self.draw_campaign_editor(gfx),
|
||||
Page::Login => self.draw_login(gfx, cloud),
|
||||
Page::Account => self.draw_account(gfx, cloud),
|
||||
Page::Match { .. } => self.draw_match(gfx, cloud),
|
||||
Page::Briefing { mission, ingame } => self.draw_briefing(gfx, mission, ingame),
|
||||
Page::Bestiary { tab, sel, level, .. } => self.draw_bestiary(gfx, tab, sel, level),
|
||||
Page::KeyCapture { action, .. } => {
|
||||
self.draw_settings(gfx, settings, 4);
|
||||
self.draw_settings(gfx, settings, 5);
|
||||
self.buttons.clear();
|
||||
let a = all_actions()[action];
|
||||
let text = trf("set.press_key", &[action_label(a)]);
|
||||
@@ -770,14 +857,15 @@ impl Menu {
|
||||
items.push((Btn::Continue, tr("menu.continue")));
|
||||
}
|
||||
items.push((Btn::Campaign, tr("menu.campaign")));
|
||||
items.push((Btn::CampaignEditor, tr("menu.campaign_editor")));
|
||||
items.push((Btn::Match, tr("menu.multiplayer")));
|
||||
items.push((Btn::New, tr("menu.new")));
|
||||
items.push((Btn::Load, tr("menu.load")));
|
||||
items.push((Btn::Settings, tr("menu.settings")));
|
||||
if let Some(s) = cloud.session.as_ref().filter(|_| cloud.available()) {
|
||||
items.push((Btn::Account, trf("menu.account", &[s.name.clone()])));
|
||||
items.push((Btn::Standalone, tr("menu.standalone")));
|
||||
}
|
||||
items.push((Btn::Standalone, tr("menu.standalone")));
|
||||
items.push((Btn::Bestiary, tr("menu.bestiary")));
|
||||
items.push((Btn::Help, tr("menu.help")));
|
||||
let w = 26.0 * CW();
|
||||
@@ -928,7 +1016,7 @@ impl Menu {
|
||||
tx += w + CW();
|
||||
}
|
||||
let top = ty + CH() * 3.5;
|
||||
if tab == 4 {
|
||||
if tab == 5 {
|
||||
// key bindings in two columns
|
||||
let acts = all_actions();
|
||||
let half = (acts.len() + 1) / 2;
|
||||
@@ -1148,6 +1236,141 @@ impl Menu {
|
||||
self.buttons.push((br, Btn::Back));
|
||||
}
|
||||
|
||||
// First campaign-editor step: load/save shareable missions and show their branching structure.
|
||||
fn draw_campaign_editor(&mut self, gfx: &Gfx) {
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
let (mx, my) = mouse();
|
||||
let cols = ((sw / CW()) as i32 - 2).min(72);
|
||||
let rows = ((sh / CH()) as i32 - 2).min(34);
|
||||
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
|
||||
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("campaign.title"), FRAME_HI);
|
||||
|
||||
let campaign = self.campaign_doc.clone();
|
||||
let errors = campaign.validate();
|
||||
let name = if campaign.name.trim().is_empty() { tr("campaign.new") } else { campaign.name.clone() };
|
||||
let file = campaign.file_name();
|
||||
let n = campaign.missions.len();
|
||||
let inner_x = x + CW() * 2.0;
|
||||
let inner_w = (cols - 4) as f32 * CW();
|
||||
let head = trf("campaign.name", &[name]);
|
||||
let head_w = (gfx.measure(&head, S()) + CW()).max(CW() * 8.0);
|
||||
let head_rect = Rect::new(inner_x, y + CH() * 1.5, head_w, GH * S() + CH() * 0.5);
|
||||
let hot_name = head_rect.contains(vec2(mx, my));
|
||||
gfx.text(&head, inner_x, y + CH() * 1.5, S(), if hot_name { FRAME_HI } else { GOLD });
|
||||
if !campaign.author.trim().is_empty() {
|
||||
let author = trf("campaign.author", &[campaign.author.clone()]);
|
||||
gfx.text(&author, inner_x + head_w + CW() * 2.0, y + CH() * 1.5, S(), DIM);
|
||||
}
|
||||
self.buttons.push((head_rect, Btn::CampaignEditName));
|
||||
|
||||
let top = if n > 0 { y + CH() * 5.0 } else { y + CH() * 3.5 };
|
||||
if n > 0 {
|
||||
let field_items = [(Btn::CampaignEditTitle, "T"), (Btn::CampaignEditGoals, "G"), (Btn::CampaignEditNext, "N"), (Btn::CampaignEditBranches, "B"), (Btn::CampaignEditScript, "S")];
|
||||
let widths: Vec<f32> = field_items.iter().map(|(_, l)| (gfx.measure(l, S()) + CW() * 2.5).max(CW() * 3.5)).collect();
|
||||
let total_w = widths.iter().sum::<f32>() + (field_items.len() as f32 - 1.0) * CW() * 0.5;
|
||||
let mut fx = x + cols as f32 * CW() - CW() * 2.0 - total_w;
|
||||
for (k, (b, l)) in field_items.iter().enumerate() {
|
||||
let r = Rect::new(fx, y + CH() * 2.8, widths[k], CH() * 1.8);
|
||||
button(gfx, r, l, r.contains(vec2(mx, my)), FRAME);
|
||||
self.buttons.push((r, *b));
|
||||
fx += widths[k] + CW() * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
let bottom = y + (rows - 5) as f32 * CH();
|
||||
let row_h = CH() * 3.0;
|
||||
self.rows_visible = (((bottom - top) / row_h).floor() as usize).max(1);
|
||||
self.scroll = self.scroll.min(n.saturating_sub(self.rows_visible));
|
||||
let scroll_w = if n > self.rows_visible { CW() * 3.0 } else { 0.0 };
|
||||
|
||||
if n == 0 {
|
||||
gfx.text(&tr("campaign.empty"), inner_x, top, S(), DIM);
|
||||
}
|
||||
|
||||
for (k, mission) in campaign.missions.iter().enumerate().skip(self.scroll).take(self.rows_visible) {
|
||||
let ry = top + (k - self.scroll) as f32 * row_h;
|
||||
let r = Rect::new(inner_x, ry, inner_w - scroll_w - CW() * 0.5, row_h - CH() * 0.5);
|
||||
let selected = k == self.campaign_selected;
|
||||
let hot = r.contains(vec2(mx, my));
|
||||
draw_rectangle(r.x, r.y, r.w, r.h, if selected { Color::new(0.32, 0.21, 0.1, 1.0) } else if hot { Color::new(0.18, 0.12, 0.08, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
|
||||
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, if selected { GOLD } else if hot { FRAME_HI } else { FRAME });
|
||||
let title = if mission.title.trim().is_empty() { mission.key.clone() } else { mission.title.clone() };
|
||||
let line = trf("campaign.mission_no", &[(k + 1).to_string(), title]);
|
||||
let maxc = ((r.w - CW() * 2.0) / CW()) as usize;
|
||||
let line: String = line.chars().take(maxc).collect();
|
||||
gfx.text(&line, r.x + CW(), r.y + CH() * 0.5, S(), if selected { GOLD } else { TXT });
|
||||
|
||||
let next = mission.next.clone().unwrap_or_default();
|
||||
let branch_count = mission.branches.len();
|
||||
let detail = if branch_count > 0 {
|
||||
trf("campaign.branches", &[branch_count.to_string()])
|
||||
} else if !next.is_empty() {
|
||||
trf("campaign.next", &[next])
|
||||
} else if !mission.goals.is_empty() {
|
||||
trf("campaign.goals", &[mission.goals.join(", ")])
|
||||
} else {
|
||||
tr("campaign.empty")
|
||||
};
|
||||
let script_count = mission.script.as_ref().map(|s| if s.source.trim().is_empty() { 0 } else { 1 }).unwrap_or(0);
|
||||
let zone_count = mission.zones.len();
|
||||
let detail = if zone_count > 0 || script_count > 0 {
|
||||
format!("{} · {} · {}", detail, trf("campaign.zones", &[zone_count.to_string()]), trf("campaign.script", &[script_count.to_string()]))
|
||||
} else {
|
||||
detail
|
||||
};
|
||||
let maxc = ((r.w - CW() * 2.0) / GW.max(1.0) / (SMALL().max(1.0))) as usize;
|
||||
let detail: String = detail.chars().take(maxc.max(20)).collect();
|
||||
gfx.text(&detail, r.x + CW(), r.y + CH() * 1.6, SMALL(), DIM);
|
||||
self.buttons.push((r, Btn::CampaignMission(k)));
|
||||
}
|
||||
|
||||
if scroll_w > 0.0 {
|
||||
let sx = inner_x + inner_w - CW() * 2.5;
|
||||
let up = Rect::new(sx, top, CW() * 2.5, CH() * 2.0);
|
||||
let down = Rect::new(sx, bottom - CH() * 2.5, CW() * 2.5, CH() * 2.0);
|
||||
button(gfx, up, "\u{25b2}", up.contains(vec2(mx, my)), FRAME);
|
||||
button(gfx, down, "\u{25bc}", down.contains(vec2(mx, my)), FRAME);
|
||||
self.buttons.push((up, Btn::Scroll(-1)));
|
||||
self.buttons.push((down, Btn::Scroll(1)));
|
||||
}
|
||||
|
||||
let count = trf("campaign.missions", &[n.to_string()]);
|
||||
gfx.text(&count, inner_x, bottom + CH() * 0.5, SMALL(), DIM);
|
||||
let file_line = format!("{} · {}", file, tr("menu.drop_hint"));
|
||||
let maxc = ((cols - 6) as f32 * CW() / (GW.max(1.0) * SMALL().max(1.0))) as usize;
|
||||
let file_line: String = file_line.chars().take(maxc.max(20)).collect();
|
||||
gfx.text(&file_line, x + cols as f32 * CW() - gfx.measure(&file_line, SMALL()) - CW() * 2.0, bottom + CH() * 0.5, SMALL(), DIM);
|
||||
|
||||
if !errors.is_empty() {
|
||||
let err = errors.iter().map(|e| e.as_str()).collect::<Vec<_>>().join(" | ");
|
||||
let maxc = ((cols - 6) as f32 * CW() / (GW.max(1.0) * SMALL().max(1.0))) as usize;
|
||||
for l in wrap(&err, maxc).iter().take(3) {
|
||||
gfx.text(l, inner_x, bottom + CH() * 1.5, SMALL(), Color::new(0.95, 0.4, 0.35, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
let by = y + (rows as f32 - 3.5) * CH();
|
||||
let right = x + cols as f32 * CW() - CW() * 2.0;
|
||||
let mut bx = inner_x;
|
||||
let mut actions = vec![(Btn::CampaignNew, tr("campaign.new")), (Btn::CampaignAdd, tr("campaign.add"))];
|
||||
if n > 0 {
|
||||
actions.push((Btn::CampaignStart, tr("campaign.start")));
|
||||
actions.push((Btn::CampaignDelete, tr("campaign.delete")));
|
||||
}
|
||||
actions.extend([(Btn::CampaignOpen, tr("campaign.open")), (Btn::CampaignSave, tr("campaign.save")), (Btn::Back, tr("menu.back"))]);
|
||||
for (b, label) in actions {
|
||||
let w = (gfx.measure(&label, S()) + CW() * 4.0).max(CW() * 6.0);
|
||||
let r = Rect::new(bx, by, w, CH() * 2.0);
|
||||
if r.x + r.w <= right {
|
||||
button(gfx, r, &label, r.contains(vec2(mx, my)), FRAME);
|
||||
self.buttons.push((r, b));
|
||||
}
|
||||
bx += w + CW();
|
||||
}
|
||||
}
|
||||
|
||||
// Briefing: the old imp tells the story, then the goals and what comes along
|
||||
fn draw_briefing(&mut self, gfx: &Gfx, mission: usize, ingame: bool) {
|
||||
let sw = sw();
|
||||
@@ -1236,7 +1459,7 @@ impl Menu {
|
||||
|
||||
// scrolling on pages with lists (mouse wheel)
|
||||
pub fn scrollable(&self) -> bool {
|
||||
matches!(self.page, Some(Page::Saves { .. }) | Some(Page::Campaign) | Some(Page::Briefing { .. }))
|
||||
matches!(self.page, Some(Page::Saves { .. }) | Some(Page::Campaign) | Some(Page::CampaignEditor) | Some(Page::Briefing { .. }))
|
||||
}
|
||||
|
||||
pub fn scroll_lines(&mut self, d: i32) {
|
||||
|
||||
Reference in new issue
Block a user