diff --git a/src/main/java/cn/pupperclient/PupperClient.java b/src/main/java/cn/pupperclient/PupperClient.java index 403f548..2ed43ad 100644 --- a/src/main/java/cn/pupperclient/PupperClient.java +++ b/src/main/java/cn/pupperclient/PupperClient.java @@ -15,7 +15,6 @@ import cn.pupperclient.management.profile.ProfileManager; import cn.pupperclient.management.websocket.WebSocketManager; import cn.pupperclient.skia.font.Fonts; -import cn.pupperclient.utils.system.ExternalToolManager; import cn.pupperclient.utils.minecraft.interfaces.IMinecraft; import cn.pupperclient.utils.thread.Multithreading; import cn.pupperclient.utils.file.FileLocation; @@ -44,8 +43,6 @@ public class PupperClient implements IMinecraft { public static final Logger LOGGER = PupperLogger.getLogger(); private final ViaFabricPlusBase viaPlatform = ViaFabricPlus.getImpl(); - private ExternalToolManager toolManager; - private MusicToolStatus musicToolStatus = MusicToolStatus.CHECKING; private long launchTime; private ModManager modManager; @@ -95,7 +92,6 @@ private void initializeManagers() { hypixelManager = new HypixelManager(); keybindManager = KeybindManager.getInstance(); keybindManager.initialize(); - toolManager = new ExternalToolManager(); PupperCommand.register(); capeManager = new CapeManager(); } @@ -115,11 +111,6 @@ private void handleFirstLaunch() throws IOException { firstLaunch = true; createConfigFile(configFile); } - - if (!firstLaunch) { - setMusicToolStatus(MusicToolStatus.DONE); - } - registerTermsScreenCheck(); } @@ -210,27 +201,7 @@ public void setProtocolVersion(ProtocolVersion version) { viaPlatform.setTargetVersion(version); } - public ExternalToolManager getToolManager() { - return toolManager; - } - - public MusicToolStatus getMusicToolStatus() { - return musicToolStatus; - } - - public void setMusicToolStatus(MusicToolStatus status) { - this.musicToolStatus = status; - } - public CapeManager getCapeManager() { return capeManager; } - - public enum MusicToolStatus { - CHECKING, - INSTALLED, - DOWNLOADING, - FAILED, - DONE - } } diff --git a/src/main/java/cn/pupperclient/gui/GuiResourcePackConvert.java b/src/main/java/cn/pupperclient/gui/GuiResourcePackConvert.java index 31c83a2..7a32b25 100644 --- a/src/main/java/cn/pupperclient/gui/GuiResourcePackConvert.java +++ b/src/main/java/cn/pupperclient/gui/GuiResourcePackConvert.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -27,7 +28,7 @@ public class GuiResourcePackConvert extends Screen { - private String progress; + private String progress = "Converting..."; private Screen prevScreen; public GuiResourcePackConvert(Screen prevScreen) { @@ -133,11 +134,14 @@ private List getOldResourcePacks() { List files = new ArrayList<>(); File packDir = new File(client.runDirectory, "resourcepacks"); - for (File f : packDir.listFiles()) { - if (f.getName().endsWith(".zip")) { - files.add(f); - } - } + File[] packFiles = packDir.listFiles(); + if (packFiles != null) { + for (File f : packFiles) { + if (f.getName().endsWith(".zip")) { + files.add(f); + } + } + } return files; } @@ -149,7 +153,7 @@ private static JsonObject readJsonFromZip(ZipInputStream zipIn) throws IOExcepti while ((len = zipIn.read(buffer)) > 0) { baos.write(buffer, 0, len); } - String jsonString = baos.toString("UTF-8"); + String jsonString = baos.toString(StandardCharsets.UTF_8); return JsonParser.parseString(jsonString).getAsJsonObject(); } } diff --git a/src/main/java/cn/pupperclient/gui/MainMenuGui.java b/src/main/java/cn/pupperclient/gui/MainMenuGui.java index 971b9d4..d922215 100644 --- a/src/main/java/cn/pupperclient/gui/MainMenuGui.java +++ b/src/main/java/cn/pupperclient/gui/MainMenuGui.java @@ -37,6 +37,7 @@ import net.minecraft.client.gui.screen.option.OptionsScreen; import net.minecraft.client.gui.screen.world.SelectWorldScreen; import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.client.MinecraftClient; import ru.vidtu.ias.screen.AccountScreen; public class MainMenuGui extends SimpleSoarGui { @@ -44,12 +45,9 @@ public class MainMenuGui extends SimpleSoarGui { private final List buttons = new ArrayList<>(); private MainMenuButton settingsButton; private MainMenuButton backgroundButton; - private float lastWindowWidth = 0; - private float lastWindowHeight = 0; - private boolean wasMinimized = false; - private static boolean showCustomizationWindow = false; - private static boolean showBackgroundWindow = false; - private static Switch darkModeSwitch; + private boolean showCustomizationWindow = false; + private boolean showBackgroundWindow = false; + private Switch darkModeSwitch; private Button exitCustomizationButton; private Button exitBackgroundButton; private IconButton addBackgroundButton; @@ -65,9 +63,15 @@ public MainMenuGui() { @Override public void init() { - updateLayout(); loadBackgroundSettings(); - initCustomizationComponents(); + rebuildLayout(); + loadExistingBackgrounds(); + } + + @Override + public void resize(MinecraftClient client, int width, int height) { + super.resize(client, width, height); + rebuildLayout(); } private void loadBackgroundSettings() { @@ -87,7 +91,7 @@ private void saveBackgroundSettings() { PupperClient.getInstance().getConfigManager().save(ConfigType.MOD); } - private void updateLayout() { + private void rebuildLayout() { buttons.clear(); float scaleFactor = calculateScaleFactor(); @@ -130,12 +134,11 @@ private void updateLayout() { client.getWindow().getWidth() - buttonSize - (20 * scaleFactor), 20 * scaleFactor, buttonSize - 5, scaleFactor, () -> showCustomizationWindow = true); - lastWindowWidth = client.getWindow().getWidth(); - lastWindowHeight = client.getWindow().getHeight(); - for (MainMenuButton button : buttons) { button.setEnabled(true); } + + initCustomizationComponents(); } private void initCustomizationComponents() { @@ -196,8 +199,6 @@ public void onAction() { }); } }); - - loadExistingBackgrounds(); } private void loadExistingBackgrounds() { @@ -206,6 +207,9 @@ private void loadExistingBackgrounds() { backgroundItems.add(new BackgroundItem("background.png", null, true)); File backgroundDir = FileLocation.BACKGROUND_DIR; + if (!backgroundDir.exists()) { + backgroundDir.mkdirs(); + } if (backgroundDir.exists() && backgroundDir.isDirectory()) { File[] backgroundFiles = backgroundDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".jpg")); @@ -225,16 +229,16 @@ private void copyBackgroundFile(File selectedFile) { File targetFile = new File(FileLocation.BACKGROUND_DIR, processedName); if (targetFile.exists()) { - System.out.println("background file already exists!"); + PupperClient.LOGGER.warn("background file already exists: {}", processedName); return; } Files.copy(selectedFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - loadExistingBackgrounds(); - - // 自动选择新添加的背景 - selectedBackgroundId = processedName; - saveBackgroundSettings(); + client.execute(() -> { + loadExistingBackgrounds(); + selectedBackgroundId = processedName; + saveBackgroundSettings(); + }); } catch (IOException e) { PupperClient.LOGGER.error("Error copying background file: {}", e.getMessage(), e); @@ -268,17 +272,7 @@ private float calculateScaleFactor() { @Override public void draw(double mouseX, double mouseY) { - boolean currentlyMinimized = isWindowMinimized(); - - if (client.getWindow().getWidth() != lastWindowWidth || - client.getWindow().getHeight() != lastWindowHeight || - wasMinimized != currentlyMinimized) { - updateLayout(); - initCustomizationComponents(); - wasMinimized = currentlyMinimized; - } - - if (currentlyMinimized) { + if (isWindowMinimized()) { return; } @@ -335,6 +329,8 @@ private void drawBackgroundWindow(double mouseX, double mouseY, ColorPalette pal float panelX = centerX - panelWidth / 2; float panelY = centerY - panelHeight / 2; + backgroundScrollHelper.onUpdate(); + Skia.drawRect(0, 0, client.getWindow().getWidth(), client.getWindow().getHeight(), ColorUtils.applyAlpha(palette.getSurface(), 0.3f)); @@ -382,6 +378,11 @@ private void drawBackgroundWindow(double mouseX, double mouseY, ColorPalette pal Skia.drawOutline(itemX - 2, itemY - 2, itemWidth + 4, itemHeight + 4, 10, 3, palette.getPrimary()); } + if (!isSelected && isHovered) { + Skia.drawRoundedRect(itemX, itemY, itemWidth, itemHeight, 8, + ColorUtils.applyAlpha(palette.getPrimary(), item.focusAnimation.getValue() * 0.12f)); + } + if (item.isDefault) { Skia.drawRoundedImage("background.png", itemX, itemY, itemWidth, itemHeight, 8); } else if (item.backgroundFile != null && item.backgroundFile.exists()) { @@ -400,7 +401,6 @@ private void drawBackgroundWindow(double mouseX, double mouseY, ColorPalette pal } private void drawCustomBackground() { - // 计算视差效果 float parallaxStrength = 40; float targetParallaxX = (float) (client.mouse.getX() - client.getWindow().getWidth() / 2F) / client.getWindow().getWidth() * parallaxStrength; float targetParallaxY = (float) (client.mouse.getY() - client.getWindow().getHeight() / 2F) / client.getWindow().getHeight() * parallaxStrength; @@ -442,6 +442,15 @@ private void drawLogoIcon() { Skia.drawRoundedImage("logo.png", logoX, logoY, logoSize, logoSize, 10 * scaleFactor); } + @Override + public boolean onMouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + if (showBackgroundWindow) { + backgroundScrollHelper.onScroll(verticalAmount); + return true; + } + return super.onMouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + @Override public boolean onMousePressed(double mouseX, double mouseY, int button) { if (isWindowMinimized()) { @@ -471,7 +480,7 @@ public boolean onMousePressed(double mouseX, double mouseY, int button) { if (MouseUtils.isInside(mouseX, adjustedMouseY, itemX, itemY, itemWidth, itemHeight)) { selectedBackgroundId = item.backgroundId; - saveBackgroundSettings(); // 保存新选择的背景 + saveBackgroundSettings(); break; } } diff --git a/src/main/java/cn/pupperclient/gui/modmenu/component/SettingBar.java b/src/main/java/cn/pupperclient/gui/modmenu/component/SettingBar.java index 8ce849c..3d78956 100644 --- a/src/main/java/cn/pupperclient/gui/modmenu/component/SettingBar.java +++ b/src/main/java/cn/pupperclient/gui/modmenu/component/SettingBar.java @@ -1,6 +1,9 @@ package cn.pupperclient.gui.modmenu.component; import java.io.File; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; import cn.pupperclient.PupperClient; import cn.pupperclient.animation.SimpleAnimation; @@ -41,6 +44,112 @@ public class SettingBar extends Component { private String title, description, icon; private Component component; + private static final Map, Function> COMPONENT_FACTORIES = new LinkedHashMap<>(); + + static { + register(BooleanSetting.class, setting -> { + BooleanSetting bSetting = (BooleanSetting) setting; + Switch switchComp = new Switch(0, 0, bSetting.isEnabled()); + switchComp.setHandler(new SwitchHandler() { + @Override + public void onEnabled() { + bSetting.setEnabled(true); + } + + @Override + public void onDisabled() { + bSetting.setEnabled(false); + } + }); + return switchComp; + }); + + register(NumberSetting.class, setting -> { + NumberSetting nSetting = (NumberSetting) setting; + Slider slider = new Slider(0, 0, 200, nSetting.getValue(), nSetting.getMinValue(), nSetting.getMaxValue(), nSetting.getStep()); + slider.setHandler(new SliderHandler() { + @Override + public void onValueChanged(Object value) { + nSetting.setValue((Float) value); + } + }); + return slider; + }); + + register(ComboSetting.class, setting -> { + ComboSetting cSetting = (ComboSetting) setting; + ComboButton button = new ComboButton(0, 0, cSetting.getOptions(), cSetting.getOption()); + button.setHandler(new ComboButtonHandler() { + @Override + public void onChanged(String option) { + cSetting.setOption(option); + } + }); + return button; + }); + + register(KeybindSetting.class, setting -> { + KeybindSetting kSetting = (KeybindSetting) setting; + Keybind bind = new Keybind(0, 0, kSetting.getKey()); + bind.setHandler(new KeybindHandler() { + @Override + public void onBinded(InputUtil.Key key) { + kSetting.setKey(key); + } + }); + return bind; + }); + + register(HctColorSetting.class, setting -> { + HctColorSetting hSetting = (HctColorSetting) setting; + HctColorPicker picker = new HctColorPicker(0, 0, hSetting.getHct()); + picker.setHandler(new HctColorPickerHandler() { + @Override + public void onPicking(Hct hct) { + hSetting.setHct(hct); + } + }); + return picker; + }); + + register(StringSetting.class, setting -> { + StringSetting sSetting = (StringSetting) setting; + TextField textField = new TextField(0, 0, 150, sSetting.getValue()); + textField.setHandler(new TextHandler() { + @Override + public void onTyped(String value) { + sSetting.setValue(value); + } + }); + return textField; + }); + + register(FileSetting.class, setting -> { + FileSetting fSetting = (FileSetting) setting; + FileSelector fileSelector = new FileSelector(0, 0, fSetting.getFile(), fSetting.getExtensions()); + fileSelector.setHandler(new FileSelectorHandler() { + @Override + public void onSelect(File file) { + fSetting.setFile(file); + } + }); + return fileSelector; + }); + } + + private static void register(Class settingClass, Function factory) { + COMPONENT_FACTORIES.put(settingClass, factory); + } + + private static Component createComponent(Setting setting) { + for (Map.Entry, Function> entry : COMPONENT_FACTORIES.entrySet()) { + if (entry.getKey().isInstance(setting)) { + return entry.getValue().apply(setting); + } + } + return null; + } + public SettingBar(Setting setting, float x, float y, float width) { super(x, y); this.title = setting.getName(); @@ -48,123 +157,7 @@ public SettingBar(Setting setting, float x, float y, float width) { this.icon = setting.getIcon(); this.width = width; this.height = 68; - - if (setting instanceof BooleanSetting) { - - BooleanSetting bSetting = (BooleanSetting) setting; - Switch switchComp = new Switch(x, y, bSetting.isEnabled()); - - switchComp.setHandler(new SwitchHandler() { - - @Override - public void onEnabled() { - bSetting.setEnabled(true); - } - - @Override - public void onDisabled() { - bSetting.setEnabled(false); - } - }); - - component = switchComp; - } - - if (setting instanceof NumberSetting) { - - NumberSetting nSetting = (NumberSetting) setting; - Slider slider = new Slider(0, 0, 200, nSetting.getValue(), nSetting.getMinValue(), nSetting.getMaxValue(), - nSetting.getStep()); - - slider.setHandler(new SliderHandler() { - @Override - public void onValueChanged(Object value) { - nSetting.setValue((Float) value); - } - }); - - component = slider; - } - - if (setting instanceof ComboSetting) { - - ComboSetting cSetting = (ComboSetting) setting; - ComboButton button = new ComboButton(0, 0, cSetting.getOptions(), cSetting.getOption()); - - button.setHandler(new ComboButtonHandler() { - - @Override - public void onChanged(String option) { - cSetting.setOption(option); - } - }); - - component = button; - } - - if (setting instanceof KeybindSetting) { - - KeybindSetting kSetting = (KeybindSetting) setting; - Keybind bind = new Keybind(0, 0, kSetting.getKey()); - - bind.setHandler(new KeybindHandler() { - - @Override - public void onBinded(InputUtil.Key key) { - kSetting.setKey(key); - } - }); - - component = bind; - } - - if (setting instanceof HctColorSetting) { - - HctColorSetting hSetting = (HctColorSetting) setting; - HctColorPicker picker = new HctColorPicker(0, 0, hSetting.getHct()); - - picker.setHandler(new HctColorPickerHandler() { - - @Override - public void onPicking(Hct hct) { - hSetting.setHct(hct); - } - }); - - component = picker; - } - - if (setting instanceof StringSetting) { - - StringSetting sSetting = (StringSetting) setting; - TextField textField = new TextField(0, 0, 150, sSetting.getValue()); - - textField.setHandler(new TextHandler() { - - @Override - public void onTyped(String value) { - sSetting.setValue(value); - } - }); - - component = textField; - } - - if(setting instanceof FileSetting) { - - FileSetting fSetting = (FileSetting) setting; - FileSelector fileSelector = new FileSelector(0, 0, fSetting.getFile(), fSetting.getExtensions()); - - fileSelector.setHandler(new FileSelectorHandler() { - - @Override - public void onSelect(File file) { - fSetting.setFile(file); - } - }); - - component = fileSelector; - } + component = createComponent(setting); } @Override diff --git a/src/main/java/cn/pupperclient/gui/modmenu/pages/CosmeticsPage.java b/src/main/java/cn/pupperclient/gui/modmenu/pages/CosmeticsPage.java index 0634dad..1a6c11e 100644 --- a/src/main/java/cn/pupperclient/gui/modmenu/pages/CosmeticsPage.java +++ b/src/main/java/cn/pupperclient/gui/modmenu/pages/CosmeticsPage.java @@ -20,9 +20,11 @@ import cn.pupperclient.utils.file.FileDialog; import cn.pupperclient.utils.language.I18n; import cn.pupperclient.utils.mouse.MouseUtils; +import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; import org.lwjgl.glfw.GLFW; +import io.github.humbleui.skija.Font; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; @@ -106,18 +108,18 @@ private void uploadCape() { File targetFile = new File(FileLocation.CAPES_DIR, processedName); if (targetFile.exists()) { - System.out.println("Cape already exists!"); + PupperClient.LOGGER.warn("Cape already exists: {}", processedName); return; } try { Files.copy(selectedFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - loadExistingCapes(); + MinecraftClient.getInstance().execute(this::loadExistingCapes); } catch (IOException e) { cn.pupperclient.PupperLogger.error("CosmeticsPage", "Failed to copy uploaded cape", e); } } else { - System.out.println("Invalid cape format!"); + PupperClient.LOGGER.warn("Invalid cape format: {}", selectedFile.getAbsolutePath()); } } }); @@ -156,12 +158,11 @@ private void drawCategoryBar(double mouseX, double mouseY) { ColorPalette palette = PupperClient.getInstance().getColorManager().getPalette(); float categoryBarY = y + 56; float categoryBarHeight = 24; - float categoryBarMarginBottom = 16; float categoryX = x + 26; for (Category category : Category.values()) { String categoryName = category.getName(); - float textWidth = getEstimatedTextWidth(categoryName); + float textWidth = getTextWidth(categoryName, Fonts.getRegular(16)); float padding = 20.0f; float buttonWidth = textWidth + padding; boolean isSelected = category == selectedCategory; @@ -175,20 +176,8 @@ private void drawCategoryBar(double mouseX, double mouseY) { } } - private float getEstimatedTextWidth(String text) { - float width = 0; - float wideCharWidth = 16.0f; - float narrowCharWidth = 8.5f; - for (char c : text.toCharArray()) { - if (c >= '一' && c <= '\u9FFF' || c >= '\u3000' && c <= '\u303F' || - c >= '\u3040' && c <= 'ゟ' || c >= '゠' && c <= 'ヿ' || - c >= '\uFF00' && c <= '\uFFEF') { - width += wideCharWidth; - } else { - width += narrowCharWidth; - } - } - return width; + private float getTextWidth(String text, Font font) { + return Skia.getTextBounds(text, font).getWidth(); } private void drawMd3Style(double mouseX, double mouseY) { @@ -261,7 +250,7 @@ public void mousePressed(double mouseX, double mouseY, int button) { for (Category category : Category.values()) { String categoryName = category.getName(); - float textWidth = getEstimatedTextWidth(categoryName); + float textWidth = getTextWidth(categoryName, Fonts.getRegular(16)); float padding = 20.0f; float buttonWidth = textWidth + padding; if (MouseUtils.isInside(mouseX, relativeMouseY, categoryX, categoryBarY, buttonWidth, categoryBarHeight)) { diff --git a/src/main/java/cn/pupperclient/gui/modmenu/pages/HomePage.java b/src/main/java/cn/pupperclient/gui/modmenu/pages/HomePage.java index 3ef2ca5..30327fa 100644 --- a/src/main/java/cn/pupperclient/gui/modmenu/pages/HomePage.java +++ b/src/main/java/cn/pupperclient/gui/modmenu/pages/HomePage.java @@ -1,8 +1,8 @@ package cn.pupperclient.gui.modmenu.pages; import java.awt.Color; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import cn.pupperclient.PupperClient; import cn.pupperclient.gui.api.SoarGui; @@ -15,6 +15,8 @@ public class HomePage extends Page { private String currentTime; + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm"); + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("EEE, MMM d"); public HomePage(SoarGui parent) { super(parent, "text.home", Icon.HOME, new RightLeftTransition(true)); @@ -24,9 +26,6 @@ public HomePage(SoarGui parent) { public void init() { super.init(); - int buttonSpacing = 20; - float buttonY = y + height - 100; - updateTime(); } @@ -89,11 +88,8 @@ private void drawFeatureCard(float x, float y, float width, float height, } private void updateTime() { - SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm"); - SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, MMM d"); - - Date now = new Date(); - currentTime = timeFormat.format(now) + " | " + dateFormat.format(now); + LocalDateTime now = LocalDateTime.now(); + currentTime = TIME_FORMAT.format(now) + " | " + DATE_FORMAT.format(now); } @Override diff --git a/src/main/java/cn/pupperclient/gui/welcomegui/TermsScreen.java b/src/main/java/cn/pupperclient/gui/welcomegui/TermsScreen.java index f53d30e..2d3440a 100644 --- a/src/main/java/cn/pupperclient/gui/welcomegui/TermsScreen.java +++ b/src/main/java/cn/pupperclient/gui/welcomegui/TermsScreen.java @@ -6,6 +6,7 @@ import cn.pupperclient.skia.font.Fonts; import cn.pupperclient.ui.component.handler.impl.ButtonHandler; import cn.pupperclient.ui.component.impl.Button; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.sound.SoundEvents; @@ -25,20 +26,19 @@ public TermsScreen() { @Override public void init() { - updatePositions(); - acceptButton = new Button("text.accept", 0, 0, Button.Style.TONAL); acceptButton.setHandler(new ButtonHandler() { @Override public void onAction() { accepted = true; - client.player.playSound(SoundEvents.UI_TOAST_IN, 1.0f, 1.0f); + if (client.player != null) { + client.player.playSound(SoundEvents.UI_TOAST_IN, 1.0f, 1.0f); + } PupperClient.hasAcceptedTerms = true; client.setScreen(null); } }); - // 创建拒绝按钮 declineButton = new Button("text.decline", 0, 0, Button.Style.TONAL); declineButton.setHandler(new ButtonHandler() { @Override @@ -47,8 +47,13 @@ public void onAction() { } }); - // 更新按钮位置 - updateButtonPositions(); + rebuildLayout(); + } + + @Override + public void resize(MinecraftClient client, int width, int height) { + super.resize(client, width, height); + rebuildLayout(); } private void updatePositions() { @@ -56,52 +61,40 @@ private void updatePositions() { centerY = client.getWindow().getHeight() / 2; } - private void updateButtonPositions() { + private void rebuildLayout() { updatePositions(); - // 获取按钮宽度 float acceptWidth = acceptButton.getWidth(); float declineWidth = declineButton.getWidth(); - // 计算按钮位置 - 让两个按钮紧挨着居中显示 - float totalWidth = acceptWidth + declineWidth + 10; // 10像素间距 + float totalWidth = acceptWidth + declineWidth + 10; float startX = centerX - totalWidth / 2; acceptButton.setX(startX); acceptButton.setY(centerY + 20); - declineButton.setX(startX + acceptWidth + 10); // 10像素间距 + declineButton.setX(startX + acceptWidth + 10); declineButton.setY(centerY + 20); } @Override public void draw(double mouseX, double mouseY) { - // 更新位置 - updatePositions(); - updateButtonPositions(); - - // 绘制半透明背景 drawTranslucentBackground(); - // 绘制欢迎界面内容 renderSkijaWelcome(mouseX, mouseY); } private void drawTranslucentBackground() { - // 绘制半透明黑色背景 Color translucentBlack = new Color(0, 0, 0, 180); Skia.drawRect(0, 0, client.getWindow().getWidth(), client.getWindow().getHeight(), translucentBlack); } private void renderSkijaWelcome(double mouseX, double mouseY) { - // 绘制标题 Skia.drawFullCenteredText("Terms of Service", centerX, centerY - 60, Color.WHITE, Fonts.getRegular(20)); - // 绘制正文 - 增加与标题的间距 Skia.drawFullCenteredText("Please read and accept the Terms of Service", centerX, centerY - 20, Color.WHITE, Fonts.getRegular(14)); - // 绘制按钮 acceptButton.draw(mouseX, mouseY); declineButton.draw(mouseX, mouseY); } @@ -120,7 +113,6 @@ public boolean onMouseReleased(double mouseX, double mouseY, int button) { return true; } - // 添加一个方法来检查是否已经接受条款 public boolean isAccepted() { return accepted; } diff --git a/src/main/java/cn/pupperclient/utils/system/ExternalToolManager.java b/src/main/java/cn/pupperclient/utils/system/ExternalToolManager.java deleted file mode 100644 index 60eee91..0000000 --- a/src/main/java/cn/pupperclient/utils/system/ExternalToolManager.java +++ /dev/null @@ -1,500 +0,0 @@ -package cn.pupperclient.utils.system; - -import cn.pupperclient.PupperClient; - -import javax.net.ssl.*; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - -public class ExternalToolManager { - private static final String TOOLS_DIR = "pupper/tools"; - - // 原始GitHub链接 - private static final String YT_DLP_URL_WINDOWS_RAW = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"; - private static final String YT_DLP_URL_LINUX_RAW = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp"; - private static final String YT_DLP_URL_MAC_RAW = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos"; - - // 镜像链接(用于CN地区) - private static final String YT_DLP_URL_WINDOWS_MIRROR = "https://gh.llkk.cc/https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"; - private static final String YT_DLP_URL_LINUX_MIRROR = "https://gh.llkk.cc/https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp"; - private static final String YT_DLP_URL_MAC_MIRROR = "https://gh.llkk.cc/https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos"; - - private static final String FFMPEG_URL_RAW = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"; - private static final String FFMPEG_URL_MIRROR = "https://gh.llkk.cc/https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"; - - private File toolsDir; - private File ytDlpPath; - private File ffmpegPath; - - private boolean ytDlpAvailable = false; - private boolean ffmpegAvailable = false; - private boolean isCNRegion = false; - - // 多线程下载 - private static final int DOWNLOAD_THREADS = 4; - private static final int BUFFER_SIZE = 8192; // 8KB buffer - private static final ExecutorService downloadExecutor = Executors.newFixedThreadPool(DOWNLOAD_THREADS); - - // 下载状态 - private static float ytDlpProgress = 0f; - private static float ffmpegProgress = 0f; - private static String currentDownload = ""; - - public ExternalToolManager() { - init(); - } - - private void init() { - toolsDir = new File(TOOLS_DIR); - if (!toolsDir.exists()) { - toolsDir.mkdirs(); - } - - // 检测地区 - detectRegion(); - - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("win")) { - ytDlpPath = new File(toolsDir, "yt-dlp.exe"); - ffmpegPath = new File(toolsDir, "ffmpeg.exe"); - } else { - ytDlpPath = new File(toolsDir, "yt-dlp"); - ffmpegPath = new File(toolsDir, "ffmpeg"); - } - } - - /** - * 检测用户所在地区,判断是否为CN地区 - */ - private void detectRegion() { - try { - // 方法1: 通过系统属性判断 - String country = System.getProperty("user.country"); - String language = System.getProperty("user.language"); - - if ("CN".equalsIgnoreCase(country) || "zh".equalsIgnoreCase(language)) { - isCNRegion = true; - PupperClient.LOGGER.info("Detected CN region, using mirror links"); - return; - } - - // 方法2: 通过时区判断 - String timezone = System.getProperty("user.timezone"); - if (timezone != null && timezone.contains("Asia/Shanghai")) { - isCNRegion = true; - PupperClient.LOGGER.info("Detected CN region by timezone, using mirror links"); - return; - } - - PupperClient.LOGGER.info("Detected non-CN region, using direct GitHub links"); - - } catch (Exception e) { - // 如果检测失败,默认使用镜像链接以确保可用性 - isCNRegion = true; - PupperClient.LOGGER.warn("Region detection failed, defaulting to mirror links"); - } - } - - /** - * 根据地区选择合适的yt-dlp下载链接 - */ - private URL getYtDlpDownloadUrl() throws MalformedURLException { - String os = System.getProperty("os.name").toLowerCase(); - String url; - - if (isCNRegion) { - // CN地区使用镜像 - if (os.contains("win")) { - url = YT_DLP_URL_WINDOWS_MIRROR; - } else if (os.contains("mac")) { - url = YT_DLP_URL_MAC_MIRROR; - } else { - url = YT_DLP_URL_LINUX_MIRROR; - } - } else { - // 非CN地区使用原始GitHub链接 - if (os.contains("win")) { - url = YT_DLP_URL_WINDOWS_RAW; - } else if (os.contains("mac")) { - url = YT_DLP_URL_MAC_RAW; - } else { - url = YT_DLP_URL_LINUX_RAW; - } - } - - PupperClient.LOGGER.info("Using yt-dlp download URL: {}", url); - return new URL(url); - } - - /** - * 根据地区选择合适的ffmpeg下载链接 - */ - private String getFfmpegDownloadUrl() { - String url = isCNRegion ? FFMPEG_URL_MIRROR : FFMPEG_URL_RAW; - PupperClient.LOGGER.info("Using ffmpeg download URL: {}", url); - return url; - } - - public void checkAndInstallTools(ToolInstallCallback callback) { - CompletableFuture.supplyAsync(() -> { - try { - // 步骤1: 检查工具 - callback.onProgress(PupperClient.MusicToolStatus.CHECKING, 0.1f, "检查工具..."); - - boolean ytDlpAvailable = checkYtDlp(); - boolean ffmpegAvailable = checkFfmpeg(); - - if (ytDlpAvailable && ffmpegAvailable) { - callback.onProgress(PupperClient.MusicToolStatus.INSTALLED, 1.0f, "工具已安装"); - callback.onComplete(true); - return true; - } - - // 步骤2: 并行下载缺失的工具 - callback.onProgress(PupperClient.MusicToolStatus.DOWNLOADING, 0.3f, "开始下载工具..."); - - List> downloads = new ArrayList<>(); - - if (!ytDlpAvailable) { - downloads.add(downloadYtDlp(progress -> { - ytDlpProgress = progress; - currentDownload = "YT-DLP"; - float overallProgress = 0.3f + (progress * 0.35f); - callback.onProgress(PupperClient.MusicToolStatus.DOWNLOADING, overallProgress, - String.format("下载 YT-DLP: %.0f%%", progress * 100)); - })); - } else { - ytDlpProgress = 1.0f; - } - - if (!ffmpegAvailable) { - downloads.add(downloadFfmpeg(progress -> { - ffmpegProgress = progress; - currentDownload = "FFmpeg"; - float overallProgress = 0.65f + (progress * 0.35f); - callback.onProgress(PupperClient.MusicToolStatus.DOWNLOADING, overallProgress, - String.format("下载 FFmpeg: %.0f%%", progress * 100)); - })); - } else { - ffmpegProgress = 1.0f; - } - - CompletableFuture allDownloads = CompletableFuture.allOf( - downloads.toArray(new CompletableFuture[0]) - ); - - boolean success = allDownloads.thenApply(v -> - downloads.stream().allMatch(future -> { - try { - return future.get(); - } catch (Exception e) { - return false; - } - }) - ).get(); - - if (success) { - callback.onProgress(PupperClient.MusicToolStatus.INSTALLED, 1.0f, "工具安装完成"); - callback.onComplete(true); - } else { - callback.onProgress(PupperClient.MusicToolStatus.FAILED, 1.0f, "工具安装失败"); - callback.onComplete(false); - } - - return success; - - } catch (Exception e) { - PupperClient.LOGGER.error("Tool installation failed: {}", e.getMessage()); - callback.onProgress(PupperClient.MusicToolStatus.FAILED, 1.0f, "工具安装失败: " + e.getMessage()); - callback.onComplete(false); - return false; - } - }); - } - - /** - * 优化的多线程下载方法 - */ - public CompletableFuture downloadFfmpeg(Consumer progressCallback) { - return CompletableFuture.supplyAsync(() -> { - try { - // 只在CN地区禁用SSL证书检查(因为镜像站可能需要) - if (isCNRegion) { - disableSSLCertificateChecking(); - } - - // 使用根据地区选择的下载链接 - String ffmpegUrl = getFfmpegDownloadUrl(); - File zipFile = new File(toolsDir, "ffmpeg.zip"); - - // 使用优化的下载方法 - boolean downloadSuccess = downloadFileWithProgress(new URL(ffmpegUrl), zipFile, progressCallback); - - if (!downloadSuccess) { - return false; - } - - // 简化的解压逻辑 - if (progressCallback != null) { - progressCallback.accept(0.8f); - } - - boolean extractSuccess = extractFfmpegSimple(zipFile, progressCallback); - - // 清理临时文件 - zipFile.delete(); - - return extractSuccess; - - } catch (Exception e) { - PupperClient.LOGGER.error("Failed to download ffmpeg: {}", e.getMessage()); - return false; - } - }, downloadExecutor); - } - - /** - * 优化的文件下载方法 - */ - private boolean downloadFileWithProgress(URL url, File outputFile, Consumer progressCallback) { - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(15000); - connection.setReadTimeout(30000); - connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); - - // 获取文件大小 - int fileSize = connection.getContentLength(); - - try (InputStream inputStream = connection.getInputStream(); - FileOutputStream outputStream = new FileOutputStream(outputFile)) { - - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - AtomicLong totalRead = new AtomicLong(0); - long lastProgressUpdate = 0; - - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - totalRead.addAndGet(bytesRead); - - // 限制进度更新频率(每100ms更新一次) - long currentTime = System.currentTimeMillis(); - if (fileSize > 0 && (currentTime - lastProgressUpdate > 100 || totalRead.get() == fileSize)) { - float progress = (float) totalRead.get() / fileSize; - if (progressCallback != null) { - progressCallback.accept(progress); - } - lastProgressUpdate = currentTime; - } - } - } - - return true; - } catch (Exception e) { - PupperClient.LOGGER.error("File download failed: {}", e.getMessage(), e); - return false; - } - } - - private boolean extractFfmpegSimple(File zipFile, Consumer progressCallback) { - try { - java.util.zip.ZipFile zip = new java.util.zip.ZipFile(zipFile); - java.util.Enumeration entries = zip.entries(); - - while (entries.hasMoreElements()) { - java.util.zip.ZipEntry entry = entries.nextElement(); - String entryName = entry.getName(); - - // 寻找 ffmpeg.exe(通常在 bin 目录下) - if (entryName.endsWith("ffmpeg.exe") && !entry.isDirectory()) { - try (InputStream inputStream = zip.getInputStream(entry); - FileOutputStream outputStream = new FileOutputStream(new File(toolsDir, "ffmpeg.exe"))) { - - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - - PupperClient.LOGGER.info("Successfully extracted ffmpeg.exe"); - break; - } - } - } - - zip.close(); - - if (progressCallback != null) { - progressCallback.accept(1.0f); - } - - // 验证文件 - File ffmpegExe = new File(toolsDir, "ffmpeg.exe"); - return ffmpegExe.exists() && ffmpegExe.length() > 0; - - } catch (Exception e) { - PupperClient.LOGGER.error("Failed to extract ffmpeg: {}", e.getMessage()); - return false; - } - } - - public CompletableFuture downloadYtDlp(Consumer progressCallback) { - return CompletableFuture.supplyAsync(() -> { - try { - // 只在CN地区禁用SSL证书检查 - if (isCNRegion) { - disableSSLCertificateChecking(); - } - - URL url = getYtDlpDownloadUrl(); - File outputFile = new File(toolsDir, "yt-dlp.exe"); - - // 使用优化的下载方法 - return downloadFileWithProgress(url, outputFile, progressCallback); - - } catch (Exception e) { - PupperClient.LOGGER.error("Failed to download yt-dlp: {}", e.getMessage()); - return false; - } - }, downloadExecutor); - } - - private boolean checkYtDlp() { - // 检查系统PATH中的yt-dlp - if (checkCommand("yt-dlp --version")) { - ytDlpAvailable = true; - ytDlpPath = new File("yt-dlp"); - } - - // 检查工具目录中是否已有工具 - if (!ytDlpAvailable && ytDlpPath.exists() && ytDlpPath.canExecute()) { - ytDlpAvailable = true; - } - - return ytDlpAvailable; - } - - public boolean checkFfmpeg() { - try { - File ffmpegExe = new File(toolsDir, "ffmpeg.exe"); - if (!ffmpegExe.exists()) { - return false; - } - - // 验证 FFmpeg 是否可执行 - ProcessBuilder processBuilder = new ProcessBuilder(ffmpegExe.getAbsolutePath(), "-version"); - processBuilder.redirectErrorStream(true); - Process process = processBuilder.start(); - - // 等待进程完成 - boolean finished = process.waitFor(5, TimeUnit.SECONDS); - if (!finished) { - process.destroy(); - return false; - } - - int exitCode = process.exitValue(); - return exitCode == 0; - - } catch (Exception e) { - PupperClient.LOGGER.error("Error checking ffmpeg: {}", e.getMessage()); - return false; - } - } - - private boolean checkCommand(String command) { - try { - ProcessBuilder pb = new ProcessBuilder(command.split(" ")); - Process process = pb.start(); - int exitCode = process.waitFor(); - return exitCode == 0; - } catch (Exception e) { - return false; - } - } - - public boolean areToolsAvailable() { - return ytDlpAvailable && ffmpegAvailable; - } - - public File getYtDlpPath() { - return ytDlpPath; - } - - public File getFfmpegPath() { - return ffmpegPath; - } - - /** - * 获取当前地区检测结果 - */ - public boolean isCNRegion() { - return isCNRegion; - } - - /** - * 获取下载进度信息 - */ - public static float getYtDlpProgress() { - return ytDlpProgress; - } - - public static float getFfmpegProgress() { - return ffmpegProgress; - } - - public static String getCurrentDownload() { - return currentDownload; - } - - /** - * 重置下载进度 - */ - public static void resetProgress() { - ytDlpProgress = 0f; - ffmpegProgress = 0f; - currentDownload = ""; - } - - private static void disableSSLCertificateChecking() { - try { - TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - } - }; - - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - - HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); - } catch (Exception e) { - PupperClient.LOGGER.error("Failed to disable SSL certificate checking: {}", e.getMessage(), e); - } - } -} diff --git a/src/main/java/cn/pupperclient/utils/system/ToolInstallCallback.java b/src/main/java/cn/pupperclient/utils/system/ToolInstallCallback.java deleted file mode 100644 index 08c9ea7..0000000 --- a/src/main/java/cn/pupperclient/utils/system/ToolInstallCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.pupperclient.utils.system; - -import cn.pupperclient.PupperClient; - -public interface ToolInstallCallback { - void onProgress(PupperClient.MusicToolStatus status, float progress, String message); - void onComplete(boolean success); -}