RecipeBookComponent.java

net.minecraft.client.gui.screens.recipebook.RecipeBookComponent

信息

  • 全限定名:net.minecraft.client.gui.screens.recipebook.RecipeBookComponent
  • 类型:public abstract class
  • 包:net.minecraft.client.gui.screens.recipebook
  • 源码路径:src/main/java/net/minecraft/client/gui/screens/recipebook/RecipeBookComponent.java
  • 起始行号:L58
  • 实现:GuiEventListener, Renderable, NarratableEntry
  • 职责:

    TODO

字段/常量

  • RECIPE_BUTTON_SPRITES

    • 类型: WidgetSprites
    • 修饰符: public static final
    • 源码定位: L59
    • 说明:

      TODO

  • RECIPE_BOOK_LOCATION

    • 类型: Identifier
    • 修饰符: protected static final
    • 源码定位: L62
    • 说明:

      TODO

  • BACKGROUND_TEXTURE_WIDTH

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L63
    • 说明:

      TODO

  • BACKGROUND_TEXTURE_HEIGHT

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L64
    • 说明:

      TODO

  • SEARCH_HINT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L65
    • 说明:

      TODO

  • IMAGE_WIDTH

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L66
    • 说明:

      TODO

  • IMAGE_HEIGHT

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L67
    • 说明:

      TODO

  • OFFSET_X_POSITION

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L68
    • 说明:

      TODO

  • BORDER_WIDTH

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L69
    • 说明:

      TODO

  • ALL_RECIPES_TOOLTIP

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L70
    • 说明:

      TODO

  • TICKS_TO_SWAP_SLOT

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L71
    • 说明:

      TODO

  • xOffset

    • 类型: int
    • 修饰符: private
    • 源码定位: L72
    • 说明:

      TODO

  • width

    • 类型: int
    • 修饰符: private
    • 源码定位: L73
    • 说明:

      TODO

  • height

    • 类型: int
    • 修饰符: private
    • 源码定位: L74
    • 说明:

      TODO

  • time

    • 类型: float
    • 修饰符: private
    • 源码定位: L75
    • 说明:

      TODO

  • lastPlacedRecipe

    • 类型: RecipeDisplayId
    • 修饰符: private
    • 源码定位: L76
    • 说明:

      TODO

  • ghostSlots

    • 类型: GhostSlots
    • 修饰符: private final
    • 源码定位: L77
    • 说明:

      TODO

  • tabButtons

    • 类型: List<RecipeBookTabButton>
    • 修饰符: private final
    • 源码定位: L78
    • 说明:

      TODO

  • selectedTab

    • 类型: RecipeBookTabButton
    • 修饰符: private
    • 源码定位: L79
    • 说明:

      TODO

  • filterButton

    • 类型: CycleButton<Boolean>
    • 修饰符: protected
    • 源码定位: L80
    • 说明:

      TODO

  • menu

    • 类型: T
    • 修饰符: protected final
    • 源码定位: L81
    • 说明:

      TODO

  • minecraft

    • 类型: Minecraft
    • 修饰符: protected
    • 源码定位: L82
    • 说明:

      TODO

  • searchBox

    • 类型: EditBox
    • 修饰符: private
    • 源码定位: L83
    • 说明:

      TODO

  • lastSearch

    • 类型: String
    • 修饰符: private
    • 源码定位: L84
    • 说明:

      TODO

  • tabInfos

    • 类型: List<RecipeBookComponent.TabInfo>
    • 修饰符: private final
    • 源码定位: L85
    • 说明:

      TODO

  • book

    • 类型: ClientRecipeBook
    • 修饰符: private
    • 源码定位: L86
    • 说明:

      TODO

  • recipeBookPage

    • 类型: RecipeBookPage
    • 修饰符: private final
    • 源码定位: L87
    • 说明:

      TODO

  • lastRecipe

    • 类型: RecipeDisplayId
    • 修饰符: private
    • 源码定位: L88
    • 说明:

      TODO

  • lastRecipeCollection

    • 类型: RecipeCollection
    • 修饰符: private
    • 源码定位: L89
    • 说明:

      TODO

  • stackedContents

    • 类型: StackedItemContents
    • 修饰符: private final
    • 源码定位: L90
    • 说明:

      TODO

  • timesInventoryChanged

    • 类型: int
    • 修饰符: private
    • 源码定位: L91
    • 说明:

      TODO

  • ignoreTextInput

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L92
    • 说明:

      TODO

  • visible

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L93
    • 说明:

      TODO

  • widthTooNarrow

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L94
    • 说明:

      TODO

  • magnifierIconPlacement

    • 类型: ScreenRectangle
    • 修饰符: private
    • 源码定位: L95
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.gui.screens.recipebook.RecipeBookComponent.TabInfo
    • 类型: record
    • 修饰符: public
    • 源码定位: L582
    • 说明:

      TODO

构造器

public RecipeBookComponent(T menu, List<RecipeBookComponent.TabInfo> tabInfos) @ L97

  • 构造器名:RecipeBookComponent
  • 源码定位:L97
  • 修饰符:public

参数:

  • menu: T
  • tabInfos: List<RecipeBookComponent.TabInfo>

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void init(int width, int height, Minecraft minecraft, boolean widthTooNarrow) @ L105

  • 方法名:init
  • 源码定位:L105
  • 返回类型:void
  • 修饰符:public

参数:

  • width: int
  • height: int
  • minecraft: Minecraft
  • widthTooNarrow: boolean

说明:

TODO

private void initVisuals() @ L118

  • 方法名:initVisuals
  • 源码定位:L118
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private int getYOrigin() @ L166

  • 方法名:getYOrigin
  • 源码定位:L166
  • 返回类型:int
  • 修饰符:private

参数:

说明:

TODO

private int getXOrigin() @ L170

  • 方法名:getXOrigin
  • 源码定位:L170
  • 返回类型:int
  • 修饰符:private

参数:

说明:

TODO

protected abstract WidgetSprites getFilterButtonTextures() @ L174

  • 方法名:getFilterButtonTextures
  • 源码定位:L174
  • 返回类型:WidgetSprites
  • 修饰符:protected abstract

参数:

说明:

TODO

public int updateScreenPosition(int width, int imageWidth) @ L176

  • 方法名:updateScreenPosition
  • 源码定位:L176
  • 返回类型:int
  • 修饰符:public

参数:

  • width: int
  • imageWidth: int

说明:

TODO

public void toggleVisibility() @ L187

  • 方法名:toggleVisibility
  • 源码定位:L187
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public boolean isVisible() @ L191

  • 方法名:isVisible
  • 源码定位:L191
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

private boolean isVisibleAccordingToBookData() @ L195

  • 方法名:isVisibleAccordingToBookData
  • 源码定位:L195
  • 返回类型:boolean
  • 修饰符:private

参数:

说明:

TODO

protected void setVisible(boolean visible) @ L199

  • 方法名:setVisible
  • 源码定位:L199
  • 返回类型:void
  • 修饰符:protected

参数:

  • visible: boolean

说明:

TODO

protected abstract boolean isCraftingSlot(Slot slot) @ L213

  • 方法名:isCraftingSlot
  • 源码定位:L213
  • 返回类型:boolean
  • 修饰符:protected abstract

参数:

  • slot: Slot

说明:

TODO

public void slotClicked(Slot slot) @ L215

  • 方法名:slotClicked
  • 源码定位:L215
  • 返回类型:void
  • 修饰符:public

参数:

  • slot: Slot

说明:

TODO

private void selectMatchingRecipes() @ L225

  • 方法名:selectMatchingRecipes
  • 源码定位:L225
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

protected abstract void selectMatchingRecipes(RecipeCollection collection, StackedItemContents stackedContents) @ L233

  • 方法名:selectMatchingRecipes
  • 源码定位:L233
  • 返回类型:void
  • 修饰符:protected abstract

参数:

  • collection: RecipeCollection
  • stackedContents: StackedItemContents

说明:

TODO

private void updateCollections(boolean resetPage, boolean isFiltering) @ L235

  • 方法名:updateCollections
  • 源码定位:L235
  • 返回类型:void
  • 修饰符:private

参数:

  • resetPage: boolean
  • isFiltering: boolean

说明:

TODO

private void updateTabs(boolean isFiltering) @ L257

  • 方法名:updateTabs
  • 源码定位:L257
  • 返回类型:void
  • 修饰符:private

参数:

  • isFiltering: boolean

说明:

TODO

public void tick() @ L275

  • 方法名:tick
  • 源码定位:L275
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

private void updateStackedContents() @ L289

  • 方法名:updateStackedContents
  • 源码定位:L289
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private boolean isFiltering() @ L297

  • 方法名:isFiltering
  • 源码定位:L297
  • 返回类型:boolean
  • 修饰符:private

参数:

说明:

TODO

public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) @ L301

  • 方法名:extractRenderState
  • 源码定位:L301
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • mouseX: int
  • mouseY: int
  • a: float

说明:

TODO

public void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY, Slot hoveredSlot) @ L322

  • 方法名:extractTooltip
  • 源码定位:L322
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • mouseX: int
  • mouseY: int
  • hoveredSlot: Slot

说明:

TODO

protected abstract Component getRecipeFilterName() @ L329

  • 方法名:getRecipeFilterName
  • 源码定位:L329
  • 返回类型:Component
  • 修饰符:protected abstract

参数:

说明:

TODO

public void extractGhostRecipe(GuiGraphicsExtractor graphics, boolean isResultSlotBig) @ L331

  • 方法名:extractGhostRecipe
  • 源码定位:L331
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • isResultSlotBig: boolean

说明:

TODO

public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) @ L335

  • 方法名:mouseClicked
  • 源码定位:L335
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: MouseButtonEvent
  • doubleClick: boolean

说明:

TODO

public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) @ L383

  • 方法名:mouseDragged
  • 源码定位:L383
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: MouseButtonEvent
  • dx: double
  • dy: double

说明:

TODO

private boolean tryPlaceRecipe(RecipeCollection recipeCollection, RecipeDisplayId recipe, boolean useMaxItems) @ L388

  • 方法名:tryPlaceRecipe
  • 源码定位:L388
  • 返回类型:boolean
  • 修饰符:private

参数:

  • recipeCollection: RecipeCollection
  • recipe: RecipeDisplayId
  • useMaxItems: boolean

说明:

TODO

private void onTabButtonPress(Button button) @ L399

  • 方法名:onTabButtonPress
  • 源码定位:L399
  • 返回类型:void
  • 修饰符:private

参数:

  • button: Button

说明:

TODO

private void replaceSelected(RecipeBookTabButton tabButton) @ L406

  • 方法名:replaceSelected
  • 源码定位:L406
  • 返回类型:void
  • 修饰符:private

参数:

  • tabButton: RecipeBookTabButton

说明:

TODO

private void toggleFiltering() @ L415

  • 方法名:toggleFiltering
  • 源码定位:L415
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

public boolean hasClickedOutside(double mx, double my, int leftPos, int topPos, int imageWidth, int imageHeight) @ L421

  • 方法名:hasClickedOutside
  • 源码定位:L421
  • 返回类型:boolean
  • 修饰符:public

参数:

  • mx: double
  • my: double
  • leftPos: int
  • topPos: int
  • imageWidth: int
  • imageHeight: int

说明:

TODO

public boolean keyPressed(KeyEvent event) @ L431

  • 方法名:keyPressed
  • 源码定位:L431
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: KeyEvent

说明:

TODO

public boolean keyReleased(KeyEvent event) @ L456

  • 方法名:keyReleased
  • 源码定位:L456
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: KeyEvent

说明:

TODO

public boolean charTyped(CharacterEvent event) @ L462

  • 方法名:charTyped
  • 源码定位:L462
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: CharacterEvent

说明:

TODO

public boolean preeditUpdated(PreeditEvent event) @ L476

  • 方法名:preeditUpdated
  • 源码定位:L476
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: PreeditEvent

说明:

TODO

public boolean isMouseOver(double mouseX, double mouseY) @ L487

  • 方法名:isMouseOver
  • 源码定位:L487
  • 返回类型:boolean
  • 修饰符:public

参数:

  • mouseX: double
  • mouseY: double

说明:

TODO

public void setFocused(boolean focused) @ L492

  • 方法名:setFocused
  • 源码定位:L492
  • 返回类型:void
  • 修饰符:public

参数:

  • focused: boolean

说明:

TODO

public boolean isFocused() @ L496

  • 方法名:isFocused
  • 源码定位:L496
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

private void checkSearchStringUpdate() @ L501

  • 方法名:checkSearchStringUpdate
  • 源码定位:L501
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private void pirateSpeechForThePeople(String searchTarget) @ L510

  • 方法名:pirateSpeechForThePeople
  • 源码定位:L510
  • 返回类型:void
  • 修饰符:private

参数:

  • searchTarget: String

说明:

TODO

private boolean isOffsetNextToMainGUI() @ L526

  • 方法名:isOffsetNextToMainGUI
  • 源码定位:L526
  • 返回类型:boolean
  • 修饰符:private

参数:

说明:

TODO

public void recipesUpdated() @ L530

  • 方法名:recipesUpdated
  • 源码定位:L530
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public void recipeShown(RecipeDisplayId recipe) @ L538

  • 方法名:recipeShown
  • 源码定位:L538
  • 返回类型:void
  • 修饰符:public

参数:

  • recipe: RecipeDisplayId

说明:

TODO

public void fillGhostRecipe(RecipeDisplay recipe) @ L542

  • 方法名:fillGhostRecipe
  • 源码定位:L542
  • 返回类型:void
  • 修饰符:public

参数:

  • recipe: RecipeDisplay

说明:

TODO

protected abstract void fillGhostRecipe(GhostSlots ghostSlots, RecipeDisplay recipe, ContextMap context) @ L548

  • 方法名:fillGhostRecipe
  • 源码定位:L548
  • 返回类型:void
  • 修饰符:protected abstract

参数:

  • ghostSlots: GhostSlots
  • recipe: RecipeDisplay
  • context: ContextMap

说明:

TODO

protected void sendUpdateSettings() @ L550

  • 方法名:sendUpdateSettings
  • 源码定位:L550
  • 返回类型:void
  • 修饰符:protected

参数:

说明:

TODO

public NarratableEntry.NarrationPriority narrationPriority() @ L559

  • 方法名:narrationPriority
  • 源码定位:L559
  • 返回类型:NarratableEntry.NarrationPriority
  • 修饰符:public

参数:

说明:

TODO

public void updateNarration(NarrationElementOutput output) @ L564

  • 方法名:updateNarration
  • 源码定位:L564
  • 返回类型:void
  • 修饰符:public

参数:

  • output: NarrationElementOutput

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public abstract class RecipeBookComponent<T extends RecipeBookMenu> implements GuiEventListener, Renderable, NarratableEntry {
    public static final WidgetSprites RECIPE_BUTTON_SPRITES = new WidgetSprites(
        Identifier.withDefaultNamespace("recipe_book/button"), Identifier.withDefaultNamespace("recipe_book/button_highlighted")
    );
    protected static final Identifier RECIPE_BOOK_LOCATION = Identifier.withDefaultNamespace("textures/gui/recipe_book.png");
    private static final int BACKGROUND_TEXTURE_WIDTH = 256;
    private static final int BACKGROUND_TEXTURE_HEIGHT = 256;
    private static final Component SEARCH_HINT = Component.translatable("gui.recipebook.search_hint").withStyle(EditBox.SEARCH_HINT_STYLE);
    public static final int IMAGE_WIDTH = 147;
    public static final int IMAGE_HEIGHT = 166;
    private static final int OFFSET_X_POSITION = 86;
    private static final int BORDER_WIDTH = 8;
    private static final Component ALL_RECIPES_TOOLTIP = Component.translatable("gui.recipebook.toggleRecipes.all");
    private static final int TICKS_TO_SWAP_SLOT = 30;
    private int xOffset;
    private int width;
    private int height;
    private float time;
    private @Nullable RecipeDisplayId lastPlacedRecipe;
    private final GhostSlots ghostSlots;
    private final List<RecipeBookTabButton> tabButtons = Lists.newArrayList();
    private @Nullable RecipeBookTabButton selectedTab;
    protected CycleButton<Boolean> filterButton;
    protected final T menu;
    protected Minecraft minecraft;
    private @Nullable EditBox searchBox;
    private String lastSearch = "";
    private final List<RecipeBookComponent.TabInfo> tabInfos;
    private ClientRecipeBook book;
    private final RecipeBookPage recipeBookPage;
    private @Nullable RecipeDisplayId lastRecipe;
    private @Nullable RecipeCollection lastRecipeCollection;
    private final StackedItemContents stackedContents = new StackedItemContents();
    private int timesInventoryChanged;
    private boolean ignoreTextInput;
    private boolean visible;
    private boolean widthTooNarrow;
    private @Nullable ScreenRectangle magnifierIconPlacement;
 
    public RecipeBookComponent(T menu, List<RecipeBookComponent.TabInfo> tabInfos) {
        this.menu = menu;
        this.tabInfos = tabInfos;
        SlotSelectTime slotSelectTime = () -> Mth.floor(this.time / 30.0F);
        this.ghostSlots = new GhostSlots(slotSelectTime);
        this.recipeBookPage = new RecipeBookPage(this, slotSelectTime, menu instanceof AbstractFurnaceMenu);
    }
 
    public void init(int width, int height, Minecraft minecraft, boolean widthTooNarrow) {
        this.minecraft = minecraft;
        this.width = width;
        this.height = height;
        this.widthTooNarrow = widthTooNarrow;
        this.book = minecraft.player.getRecipeBook();
        this.timesInventoryChanged = minecraft.player.getInventory().getTimesChanged();
        this.visible = this.isVisibleAccordingToBookData();
        if (this.visible) {
            this.initVisuals();
        }
    }
 
    private void initVisuals() {
        boolean isFiltering = this.isFiltering();
        this.xOffset = this.widthTooNarrow ? 0 : 86;
        int xo = this.getXOrigin();
        int yo = this.getYOrigin();
        this.stackedContents.clear();
        this.minecraft.player.getInventory().fillStackedContents(this.stackedContents);
        this.menu.fillCraftSlotsStackedContents(this.stackedContents);
        String oldEdit = this.searchBox != null ? this.searchBox.getValue() : "";
        this.searchBox = new EditBox(this.minecraft.font, xo + 25, yo + 13, 81, 9 + 5, Component.translatable("itemGroup.search"));
        this.searchBox.setMaxLength(50);
        this.searchBox.setVisible(true);
        this.searchBox.setTextColor(-1);
        this.searchBox.setValue(oldEdit);
        this.searchBox.setHint(SEARCH_HINT);
        this.magnifierIconPlacement = ScreenRectangle.of(
            ScreenAxis.HORIZONTAL, xo + 8, this.searchBox.getY(), this.searchBox.getX() - this.getXOrigin(), this.searchBox.getHeight()
        );
        this.recipeBookPage.init(this.minecraft, xo, yo);
        this.filterButton = CycleButton.booleanBuilder(this.getRecipeFilterName(), ALL_RECIPES_TOOLTIP, isFiltering)
            .withTooltip(filtering -> filtering ? Tooltip.create(this.getRecipeFilterName()) : Tooltip.create(ALL_RECIPES_TOOLTIP))
            .withSprite((cycleButton, filtering) -> this.getFilterButtonTextures().get(filtering, cycleButton.isHoveredOrFocused()))
            .displayState(CycleButton.DisplayState.HIDE)
            .create(xo + 110, yo + 12, 26, 16, CommonComponents.EMPTY, (button, value) -> {
                this.toggleFiltering();
                this.sendUpdateSettings();
                this.updateCollections(false, value);
            });
        this.tabButtons.clear();
 
        for (RecipeBookComponent.TabInfo tabInfo : this.tabInfos) {
            this.tabButtons.add(new RecipeBookTabButton(0, 0, tabInfo, this::onTabButtonPress));
        }
 
        if (this.selectedTab != null) {
            this.selectedTab = this.tabButtons.stream().filter(o -> o.getCategory().equals(this.selectedTab.getCategory())).findFirst().orElse(null);
        }
 
        if (this.selectedTab == null) {
            this.selectedTab = this.tabButtons.get(0);
        }
 
        this.selectedTab.select();
        this.selectMatchingRecipes();
        this.updateTabs(isFiltering);
        this.updateCollections(false, isFiltering);
    }
 
    private int getYOrigin() {
        return (this.height - 166) / 2;
    }
 
    private int getXOrigin() {
        return (this.width - 147) / 2 - this.xOffset;
    }
 
    protected abstract WidgetSprites getFilterButtonTextures();
 
    public int updateScreenPosition(int width, int imageWidth) {
        int leftPos;
        if (this.isVisible() && !this.widthTooNarrow) {
            leftPos = 177 + (width - imageWidth - 200) / 2;
        } else {
            leftPos = (width - imageWidth) / 2;
        }
 
        return leftPos;
    }
 
    public void toggleVisibility() {
        this.setVisible(!this.isVisible());
    }
 
    public boolean isVisible() {
        return this.visible;
    }
 
    private boolean isVisibleAccordingToBookData() {
        return this.book.isOpen(this.menu.getRecipeBookType());
    }
 
    protected void setVisible(boolean visible) {
        if (visible) {
            this.initVisuals();
        }
 
        this.visible = visible;
        this.book.setOpen(this.menu.getRecipeBookType(), visible);
        if (!visible) {
            this.recipeBookPage.setInvisible();
        }
 
        this.sendUpdateSettings();
    }
 
    protected abstract boolean isCraftingSlot(Slot slot);
 
    public void slotClicked(@Nullable Slot slot) {
        if (slot != null && this.isCraftingSlot(slot)) {
            this.lastPlacedRecipe = null;
            this.ghostSlots.clear();
            if (this.isVisible()) {
                this.updateStackedContents();
            }
        }
    }
 
    private void selectMatchingRecipes() {
        for (RecipeBookComponent.TabInfo tabInfo : this.tabInfos) {
            for (RecipeCollection recipeCollection : this.book.getCollection(tabInfo.category())) {
                this.selectMatchingRecipes(recipeCollection, this.stackedContents);
            }
        }
    }
 
    protected abstract void selectMatchingRecipes(RecipeCollection collection, StackedItemContents stackedContents);
 
    private void updateCollections(boolean resetPage, boolean isFiltering) {
        List<RecipeCollection> tabCollection = this.book.getCollection(this.selectedTab.getCategory());
        List<RecipeCollection> collection = Lists.newArrayList(tabCollection);
        collection.removeIf(c -> !c.hasAnySelected());
        String searchTarget = this.searchBox.getValue();
        if (!searchTarget.isEmpty()) {
            ClientPacketListener connection = this.minecraft.getConnection();
            if (connection != null) {
                ObjectSet<RecipeCollection> set = new ObjectLinkedOpenHashSet<>(
                    connection.searchTrees().recipes().search(searchTarget.toLowerCase(Locale.ROOT))
                );
                collection.removeIf(e -> !set.contains(e));
            }
        }
 
        if (isFiltering) {
            collection.removeIf(c -> !c.hasCraftable());
        }
 
        this.recipeBookPage.updateCollections(collection, resetPage, isFiltering);
    }
 
    private void updateTabs(boolean isFiltering) {
        int xPosTab = (this.width - 147) / 2 - this.xOffset - 30;
        int yPosTab = (this.height - 166) / 2 + 3;
        int yOffset = 27;
        int index = 0;
 
        for (RecipeBookTabButton tabButton : this.tabButtons) {
            ExtendedRecipeBookCategory category = tabButton.getCategory();
            if (category instanceof SearchRecipeBookCategory) {
                tabButton.visible = true;
                tabButton.setPosition(xPosTab, yPosTab + 27 * index++);
            } else if (tabButton.updateVisibility(this.book)) {
                tabButton.setPosition(xPosTab, yPosTab + 27 * index++);
                tabButton.startAnimation(this.book, isFiltering);
            }
        }
    }
 
    public void tick() {
        boolean shouldBeVisible = this.isVisibleAccordingToBookData();
        if (this.isVisible() != shouldBeVisible) {
            this.setVisible(shouldBeVisible);
        }
 
        if (this.isVisible()) {
            if (this.timesInventoryChanged != this.minecraft.player.getInventory().getTimesChanged()) {
                this.updateStackedContents();
                this.timesInventoryChanged = this.minecraft.player.getInventory().getTimesChanged();
            }
        }
    }
 
    private void updateStackedContents() {
        this.stackedContents.clear();
        this.minecraft.player.getInventory().fillStackedContents(this.stackedContents);
        this.menu.fillCraftSlotsStackedContents(this.stackedContents);
        this.selectMatchingRecipes();
        this.updateCollections(false, this.isFiltering());
    }
 
    private boolean isFiltering() {
        return this.book.isFiltering(this.menu.getRecipeBookType());
    }
 
    @Override
    public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) {
        if (this.isVisible()) {
            if (!this.minecraft.hasControlDown()) {
                this.time += a;
            }
 
            int xo = this.getXOrigin();
            int yo = this.getYOrigin();
            graphics.blit(RenderPipelines.GUI_TEXTURED, RECIPE_BOOK_LOCATION, xo, yo, 1.0F, 1.0F, 147, 166, 256, 256);
            this.searchBox.extractRenderState(graphics, mouseX, mouseY, a);
 
            for (RecipeBookTabButton tabButton : this.tabButtons) {
                tabButton.extractRenderState(graphics, mouseX, mouseY, a);
            }
 
            this.filterButton.extractRenderState(graphics, mouseX, mouseY, a);
            this.recipeBookPage.extractRenderState(graphics, xo, yo, mouseX, mouseY, a);
        }
    }
 
    public void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY, @Nullable Slot hoveredSlot) {
        if (this.isVisible()) {
            this.recipeBookPage.extractTooltip(graphics, mouseX, mouseY);
            this.ghostSlots.extractTooltip(graphics, this.minecraft, mouseX, mouseY, hoveredSlot);
        }
    }
 
    protected abstract Component getRecipeFilterName();
 
    public void extractGhostRecipe(GuiGraphicsExtractor graphics, boolean isResultSlotBig) {
        this.ghostSlots.extractRenderState(graphics, this.minecraft, isResultSlotBig);
    }
 
    @Override
    public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
        if (this.isVisible() && !this.minecraft.player.isSpectator()) {
            if (this.recipeBookPage.mouseClicked(event, this.getXOrigin(), this.getYOrigin(), 147, 166, doubleClick)) {
                RecipeDisplayId recipe = this.recipeBookPage.getLastClickedRecipe();
                RecipeCollection recipeCollection = this.recipeBookPage.getLastClickedRecipeCollection();
                if (recipe != null && recipeCollection != null) {
                    if (!this.tryPlaceRecipe(recipeCollection, recipe, event.hasShiftDown())) {
                        return false;
                    }
 
                    this.lastRecipeCollection = recipeCollection;
                    this.lastRecipe = recipe;
                    if (!this.isOffsetNextToMainGUI()) {
                        this.setVisible(false);
                    }
                }
 
                return true;
            } else {
                if (this.searchBox != null) {
                    boolean clickedMagnifierIcon = this.magnifierIconPlacement != null
                        && this.magnifierIconPlacement.containsPoint(Mth.floor(event.x()), Mth.floor(event.y()));
                    if (clickedMagnifierIcon || this.searchBox.mouseClicked(event, doubleClick)) {
                        this.searchBox.setFocused(true);
                        return true;
                    }
 
                    this.searchBox.setFocused(false);
                }
 
                if (this.filterButton.mouseClicked(event, doubleClick)) {
                    return true;
                } else {
                    for (RecipeBookTabButton tabButton : this.tabButtons) {
                        if (tabButton.mouseClicked(event, doubleClick)) {
                            return true;
                        }
                    }
 
                    return false;
                }
            }
        } else {
            return false;
        }
    }
 
    @Override
    public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) {
        return this.searchBox != null && this.searchBox.isFocused() ? this.searchBox.mouseDragged(event, dx, dy) : false;
    }
 
    private boolean tryPlaceRecipe(RecipeCollection recipeCollection, RecipeDisplayId recipe, boolean useMaxItems) {
        if (!recipeCollection.isCraftable(recipe) && recipe.equals(this.lastPlacedRecipe)) {
            return false;
        } else {
            this.lastPlacedRecipe = recipe;
            this.ghostSlots.clear();
            this.minecraft.gameMode.handlePlaceRecipe(this.minecraft.player.containerMenu.containerId, recipe, useMaxItems);
            return true;
        }
    }
 
    private void onTabButtonPress(Button button) {
        if (this.selectedTab != button && button instanceof RecipeBookTabButton recipeBookTabButton) {
            this.replaceSelected(recipeBookTabButton);
            this.updateCollections(true, this.isFiltering());
        }
    }
 
    private void replaceSelected(RecipeBookTabButton tabButton) {
        if (this.selectedTab != null) {
            this.selectedTab.unselect();
        }
 
        tabButton.select();
        this.selectedTab = tabButton;
    }
 
    private void toggleFiltering() {
        RecipeBookType type = this.menu.getRecipeBookType();
        boolean newSetting = !this.book.isFiltering(type);
        this.book.setFiltering(type, newSetting);
    }
 
    public boolean hasClickedOutside(double mx, double my, int leftPos, int topPos, int imageWidth, int imageHeight) {
        if (!this.isVisible()) {
            return true;
        } else {
            boolean clickedOutside = mx < leftPos || my < topPos || mx >= leftPos + imageWidth || my >= topPos + imageHeight;
            boolean clickedOnRecipeBook = leftPos - 147 < mx && mx < leftPos && topPos < my && my < topPos + imageHeight;
            return clickedOutside && !clickedOnRecipeBook && !this.selectedTab.isHoveredOrFocused();
        }
    }
 
    @Override
    public boolean keyPressed(KeyEvent event) {
        this.ignoreTextInput = false;
        if (!this.isVisible() || this.minecraft.player.isSpectator()) {
            return false;
        } else if (event.isEscape() && !this.isOffsetNextToMainGUI()) {
            this.setVisible(false);
            return true;
        } else if (this.searchBox.keyPressed(event)) {
            this.checkSearchStringUpdate();
            return true;
        } else if (this.searchBox.isFocused() && this.searchBox.isVisible() && !event.isEscape()) {
            return true;
        } else if (this.minecraft.options.keyChat.matches(event) && !this.searchBox.isFocused()) {
            this.ignoreTextInput = true;
            this.searchBox.setFocused(true);
            return true;
        } else if (event.isSelection() && this.lastRecipeCollection != null && this.lastRecipe != null) {
            AbstractWidget.playButtonClickSound(Minecraft.getInstance().getSoundManager());
            return this.tryPlaceRecipe(this.lastRecipeCollection, this.lastRecipe, event.hasShiftDown());
        } else {
            return false;
        }
    }
 
    @Override
    public boolean keyReleased(KeyEvent event) {
        this.ignoreTextInput = false;
        return GuiEventListener.super.keyReleased(event);
    }
 
    @Override
    public boolean charTyped(CharacterEvent event) {
        if (this.ignoreTextInput) {
            return false;
        } else if (!this.isVisible() || this.minecraft.player.isSpectator()) {
            return false;
        } else if (this.searchBox.charTyped(event)) {
            this.checkSearchStringUpdate();
            return true;
        } else {
            return GuiEventListener.super.charTyped(event);
        }
    }
 
    @Override
    public boolean preeditUpdated(@Nullable PreeditEvent event) {
        if (this.ignoreTextInput) {
            return false;
        } else if (!this.isVisible() || this.minecraft.player.isSpectator()) {
            return false;
        } else {
            return this.searchBox.preeditUpdated(event) ? true : GuiEventListener.super.preeditUpdated(event);
        }
    }
 
    @Override
    public boolean isMouseOver(double mouseX, double mouseY) {
        return false;
    }
 
    @Override
    public void setFocused(boolean focused) {
    }
 
    @Override
    public boolean isFocused() {
        return false;
    }
 
    private void checkSearchStringUpdate() {
        String searchText = this.searchBox.getValue().toLowerCase(Locale.ROOT);
        this.pirateSpeechForThePeople(searchText);
        if (!searchText.equals(this.lastSearch)) {
            this.updateCollections(false, this.isFiltering());
            this.lastSearch = searchText;
        }
    }
 
    private void pirateSpeechForThePeople(String searchTarget) {
        if ("excitedze".equals(searchTarget)) {
            LanguageManager languageManager = this.minecraft.getLanguageManager();
            String arrrrCode = "en_pt";
            LanguageInfo language = languageManager.getLanguage("en_pt");
            if (language == null || languageManager.getSelected().equals("en_pt")) {
                return;
            }
 
            languageManager.setSelected("en_pt");
            this.minecraft.options.languageCode = "en_pt";
            this.minecraft.reloadResourcePacks();
            this.minecraft.options.save();
        }
    }
 
    private boolean isOffsetNextToMainGUI() {
        return this.xOffset == 86;
    }
 
    public void recipesUpdated() {
        this.selectMatchingRecipes();
        this.updateTabs(this.isFiltering());
        if (this.isVisible()) {
            this.updateCollections(false, this.isFiltering());
        }
    }
 
    public void recipeShown(RecipeDisplayId recipe) {
        this.minecraft.player.removeRecipeHighlight(recipe);
    }
 
    public void fillGhostRecipe(RecipeDisplay recipe) {
        this.ghostSlots.clear();
        ContextMap context = SlotDisplayContext.fromLevel(Objects.requireNonNull(this.minecraft.level));
        this.fillGhostRecipe(this.ghostSlots, recipe, context);
    }
 
    protected abstract void fillGhostRecipe(GhostSlots ghostSlots, RecipeDisplay recipe, ContextMap context);
 
    protected void sendUpdateSettings() {
        if (this.minecraft.getConnection() != null) {
            RecipeBookType type = this.menu.getRecipeBookType();
            boolean open = this.book.getBookSettings().isOpen(type);
            boolean filtering = this.book.getBookSettings().isFiltering(type);
            this.minecraft.getConnection().send(new ServerboundRecipeBookChangeSettingsPacket(type, open, filtering));
        }
    }
 
    @Override
    public NarratableEntry.NarrationPriority narrationPriority() {
        return this.visible ? NarratableEntry.NarrationPriority.HOVERED : NarratableEntry.NarrationPriority.NONE;
    }
 
    @Override
    public void updateNarration(NarrationElementOutput output) {
        List<NarratableEntry> narratableEntries = Lists.newArrayList();
        this.recipeBookPage.listButtons(e -> {
            if (e.isActive()) {
                narratableEntries.add(e);
            }
        });
        narratableEntries.add(this.searchBox);
        narratableEntries.add(this.filterButton);
        narratableEntries.addAll(this.tabButtons);
        Screen.NarratableSearchResult narratable = Screen.findNarratableWidget(narratableEntries, null);
        if (narratable != null) {
            narratable.entry().updateNarration(output.nest());
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public record TabInfo(ItemStack primaryIcon, Optional<ItemStack> secondaryIcon, ExtendedRecipeBookCategory category) {
        public TabInfo(SearchRecipeBookCategory category) {
            this(new ItemStack(Items.COMPASS), Optional.empty(), category);
        }
 
        public TabInfo(Item icon, RecipeBookCategory category) {
            this(new ItemStack(icon), Optional.empty(), category);
        }
 
        public TabInfo(Item primaryIcon, Item secondaryIcon, RecipeBookCategory category) {
            this(new ItemStack(primaryIcon), Optional.of(new ItemStack(secondaryIcon)), category);
        }
    }
}

引用的其他类