mirror of https://gitlab.com/nakst/essence
cursor locator
This commit is contained in:
parent
d74af82763
commit
fc013e8485
|
@ -260,7 +260,7 @@ EsError NodeOpen(const char *path, size_t pathBytes, uint32_t flags, _EsNodeInfo
|
|||
return EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, flags, (uintptr_t) node);
|
||||
}
|
||||
|
||||
EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes) {
|
||||
EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, bool createIfNeeded = false) {
|
||||
if (keyBytes == -1) keyBytes = EsCStringLength(key);
|
||||
|
||||
for (uintptr_t i = 0; i < group->itemCount; i++) {
|
||||
|
@ -269,10 +269,23 @@ EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup
|
|||
}
|
||||
}
|
||||
|
||||
if (createIfNeeded) {
|
||||
EsSystemConfigurationItem item = {};
|
||||
item.key = (char *) EsHeapAllocate(keyBytes, false);
|
||||
item.keyBytes = keyBytes;
|
||||
EsMemoryCopy(item.key, key, keyBytes);
|
||||
|
||||
Array<EsSystemConfigurationItem> items = { group->items };
|
||||
EsSystemConfigurationItem *_item = items.Add(item);
|
||||
group->items = items.array;
|
||||
group->itemCount++;
|
||||
return _item;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptrdiff_t sectionBytes) {
|
||||
EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptrdiff_t sectionBytes, bool createIfNeeded = false) {
|
||||
if (sectionBytes == -1) sectionBytes = EsCStringLength(section);
|
||||
|
||||
for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) {
|
||||
|
@ -281,6 +294,14 @@ EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptr
|
|||
}
|
||||
}
|
||||
|
||||
if (createIfNeeded) {
|
||||
EsSystemConfigurationGroup group = {};
|
||||
group.section = (char *) EsHeapAllocate(sectionBytes, false);
|
||||
group.sectionBytes = sectionBytes;
|
||||
EsMemoryCopy(group.section, section, sectionBytes);
|
||||
return api.systemConfigurationGroups.Add(group);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -791,7 +812,7 @@ EsMessage *EsMessageReceive() {
|
|||
EsMessageSend((EsElement *) message.object, &message.message);
|
||||
} else if (type == ES_MSG_TIMER) {
|
||||
((EsTimerCallbackFunction) message.message.user.context1.p)(message.message.user.context2);
|
||||
} else if (type >= ES_MSG_WM_START && type <= ES_MSG_WM_END) {
|
||||
} else if (type >= ES_MSG_WM_START && type <= ES_MSG_WM_END && message.object) {
|
||||
#if 0
|
||||
ProcessMessageTiming timing = {};
|
||||
double start = EsTimeStampMs();
|
||||
|
@ -802,11 +823,9 @@ EsMessage *EsMessageReceive() {
|
|||
timing.endLayout - timing.startLayout,
|
||||
timing.endPaint - timing.startPaint,
|
||||
timing.endUpdate - timing.startUpdate);
|
||||
#else
|
||||
UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr);
|
||||
#endif
|
||||
|
||||
if (message.object) {
|
||||
UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr);
|
||||
}
|
||||
} else if (type == ES_MSG_TAB_INSPECT_UI) {
|
||||
EsInstance *_instance = InstanceFromWindowID(message.message.tabOperation.id);
|
||||
|
||||
|
|
|
@ -2019,6 +2019,30 @@ void EmbeddedWindowDestroyed(EsObjectID id) {
|
|||
EsHeapFree(instance);
|
||||
}
|
||||
|
||||
int CursorLocatorMessage(EsElement *element, EsMessage *message) {
|
||||
EsWindow *window = element->window;
|
||||
|
||||
if (message->type == ES_MSG_ANIMATE) {
|
||||
window->announcementTimeMs += message->animate.deltaMs;
|
||||
double progress = window->announcementTimeMs / GetConstantNumber("cursorLocatorDuration");
|
||||
|
||||
if (progress > 1) {
|
||||
EsElementDestroy(window);
|
||||
} else {
|
||||
EsElementRelayout(element);
|
||||
message->animate.complete = false;
|
||||
return ES_HANDLED;
|
||||
}
|
||||
} else if (message->type == ES_MSG_LAYOUT) {
|
||||
EsElement *child = element->GetChild(0);
|
||||
double progress = 1.0 - window->announcementTimeMs / GetConstantNumber("cursorLocatorDuration");
|
||||
int width = progress * child->GetWidth(0), height = progress * child->GetHeight(0);
|
||||
child->InternalMove(width, height, (element->width - width) / 2, (element->height - height) / 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DesktopMessage(EsMessage *message) {
|
||||
if (message->type == ES_MSG_POWER_BUTTON_PRESSED) {
|
||||
ShutdownModalCreate();
|
||||
|
@ -2089,6 +2113,19 @@ void DesktopMessage(EsMessage *message) {
|
|||
} else {
|
||||
// The screen resolution will be correctly queried in DesktopSetup.
|
||||
}
|
||||
} else if (message->type == ES_MSG_SINGLE_CTRL_PRESS) {
|
||||
if (EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("locate_cursor_on_ctrl"))) {
|
||||
EsPoint position = EsMouseGetPosition();
|
||||
EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_TIP);
|
||||
EsElement *wrapper = EsCustomElementCreate(window, ES_CELL_FILL, ES_STYLE_CLEAR_BACKGROUND);
|
||||
wrapper->messageUser = CursorLocatorMessage;
|
||||
window->announcementBase = position;
|
||||
EsElement *element = EsCustomElementCreate(wrapper, ES_CELL_FILL, ES_STYLE_CURSOR_LOCATOR);
|
||||
int width = element->GetWidth(0), height = element->GetHeight(0);
|
||||
EsRectangle bounds = ES_RECT_4PD(position.x - width / 2, position.y - height / 2, width, height);
|
||||
EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP);
|
||||
wrapper->StartAnimating();
|
||||
}
|
||||
} else if (message->type == MSG_SETUP_DESKTOP_UI) {
|
||||
DesktopSetup();
|
||||
}
|
||||
|
|
|
@ -449,7 +449,7 @@ struct EsWindow : EsElement {
|
|||
EsElement *source; // Menu source.
|
||||
EsWindow *targetMenu; // The menu that keyboard events should be sent to.
|
||||
|
||||
int32_t announcementBaseY;
|
||||
EsPoint announcementBase;
|
||||
double announcementTimeMs;
|
||||
};
|
||||
|
||||
|
@ -3582,7 +3582,6 @@ int AnnouncementMessage(EsElement *element, EsMessage *message) {
|
|||
double progress = window->announcementTimeMs / GetConstantNumber("announcementDuration");
|
||||
|
||||
if (progress > 1) {
|
||||
progress = 1;
|
||||
EsElementDestroy(window);
|
||||
return 0;
|
||||
}
|
||||
|
@ -3595,7 +3594,7 @@ int AnnouncementMessage(EsElement *element, EsMessage *message) {
|
|||
|
||||
EsRectangle bounds = EsWindowGetBounds(window);
|
||||
int32_t height = Height(bounds);
|
||||
bounds.t = window->announcementBaseY - inOnly * GetConstantNumber("announcementMovement");
|
||||
bounds.t = window->announcementBase.y - inOnly * GetConstantNumber("announcementMovement");
|
||||
bounds.b = bounds.t + height;
|
||||
|
||||
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0xFF * inOut, 0, ES_WINDOW_PROPERTY_ALPHA);
|
||||
|
@ -3619,11 +3618,12 @@ void EsAnnouncementShow(EsWindow *parent, uint64_t flags, int32_t x, int32_t y,
|
|||
int32_t width = display->GetWidth(0);
|
||||
int32_t height = display->GetHeight(width);
|
||||
|
||||
EsRectangle parentBounds = EsWindowGetBounds(parent);
|
||||
EsRectangle parentBounds = {};
|
||||
if (parent) parentBounds = EsWindowGetBounds(parent);
|
||||
EsRectangle bounds = ES_RECT_4PD(x - width / 2 + parentBounds.l, y - height + parentBounds.t, width, height);
|
||||
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0x00, 0, ES_WINDOW_PROPERTY_ALPHA);
|
||||
EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN | ES_WINDOW_MOVE_ALWAYS_ON_TOP);
|
||||
window->announcementBaseY = EsWindowGetBounds(window).t;
|
||||
window->announcementBase.y = EsWindowGetBounds(window).t;
|
||||
window->StartAnimating();
|
||||
}
|
||||
|
||||
|
@ -6003,11 +6003,14 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) {
|
|||
return;
|
||||
}
|
||||
|
||||
EsWindow *window = gui.accessKeys.window;
|
||||
|
||||
int ic, isc;
|
||||
ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, false, false);
|
||||
ic = EsCRTtoupper(ic);
|
||||
|
||||
bool keepAccessKeyModeActive = false;
|
||||
bool regatherKeys = false;
|
||||
|
||||
if (ic >= 'A' && ic <= 'Z' && !gui.accessKeys.typedCharacter) {
|
||||
if (gui.accessKeys.numbers[ic - 'A'] > 1) {
|
||||
|
@ -6023,6 +6026,7 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) {
|
|||
EsElementFocus(entry->element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD);
|
||||
|
||||
keepAccessKeyModeActive = entry->element->flags & ES_ELEMENT_STICKY_ACCESS_KEY;
|
||||
regatherKeys = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6036,14 +6040,19 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) {
|
|||
EsElementFocus(entry->element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD);
|
||||
|
||||
keepAccessKeyModeActive = entry->element->flags & ES_ELEMENT_STICKY_ACCESS_KEY;
|
||||
regatherKeys = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepAccessKeyModeActive) {
|
||||
AccessKeyModeExit();
|
||||
} else if (regatherKeys) {
|
||||
AccessKeyModeExit();
|
||||
window->InternalMove(window->width, window->height, 0, 0);
|
||||
AccessKeyModeEnter(window);
|
||||
} else {
|
||||
gui.accessKeys.window->Repaint(true);
|
||||
window->Repaint(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -954,6 +954,7 @@ enum EsMessageType {
|
|||
ES_MSG_DESKTOP = 0x4806
|
||||
ES_MSG_DEVICE_CONNECTED = 0x4807
|
||||
ES_MSG_DEVICE_DISCONNECTED = 0x4808
|
||||
ES_MSG_SINGLE_CTRL_PRESS = 0x4809
|
||||
|
||||
// Messages sent from Desktop to application instances:
|
||||
ES_MSG_TAB_INSPECT_UI = 0x4A01
|
||||
|
|
|
@ -121,8 +121,8 @@ void SettingsNumberBoxSetValue(EsElement *element, int32_t newValue) {
|
|||
EsTextboxInsert(textbox, buffer, bytes);
|
||||
|
||||
EsMutexAcquire(&api.systemConfigurationMutex);
|
||||
EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1); // TODO Create if needed.
|
||||
EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1); // TODO Create if needed.
|
||||
EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1, true);
|
||||
EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1, true);
|
||||
int32_t oldValue = EsIntegerParse(item->value, item->valueBytes);
|
||||
EsHeapFree(item->value);
|
||||
item->value = (char *) EsHeapAllocate(65, true);
|
||||
|
@ -162,7 +162,7 @@ void SettingsAddTitle(EsElement *container, SettingsPage *page) {
|
|||
|
||||
void SettingsAddUndoButton(EsElement *stack) {
|
||||
EsPanel *overlay = EsPanelCreate(stack, ES_CELL_H_RIGHT | ES_CELL_V_TOP, &styleSettingsOverlayPanel);
|
||||
EsButton *undoButton = EsButtonCreate(overlay, ES_BUTTON_TOOLBAR, 0, INTERFACE_STRING(DesktopSettingsUndoButton));
|
||||
EsButton *undoButton = EsButtonCreate(overlay, ES_BUTTON_TOOLBAR | ES_ELEMENT_STICKY_ACCESS_KEY, 0, INTERFACE_STRING(DesktopSettingsUndoButton));
|
||||
undoButton->accessKey = 'U';
|
||||
((SettingsInstance *) stack->instance)->undoButton = undoButton;
|
||||
EsButtonSetIcon(undoButton, ES_ICON_EDIT_UNDO_SYMBOLIC);
|
||||
|
@ -184,8 +184,8 @@ void SettingsCheckboxCommand(EsInstance *_instance, EsElement *element, EsComman
|
|||
bool newValue = EsButtonGetCheck(button) == ES_CHECK_CHECKED;
|
||||
|
||||
EsMutexAcquire(&api.systemConfigurationMutex);
|
||||
EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1); // TODO Create if needed.
|
||||
EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1); // TODO Create if needed.
|
||||
EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1, true);
|
||||
EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1, true);
|
||||
bool oldValue = EsIntegerParse(item->value, item->valueBytes);
|
||||
EsHeapFree(item->value);
|
||||
item->value = (char *) EsHeapAllocate(2, true);
|
||||
|
@ -209,7 +209,7 @@ void SettingsAddCheckbox(EsElement *table, const char *string, ptrdiff_t stringB
|
|||
control->globalPointerBool = globalPointerBool;
|
||||
control->originalValueBool = EsSystemConfigurationReadInteger(control->cConfigurationSection, -1, control->cConfigurationKey, -1);
|
||||
|
||||
EsButton *button = EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX | ES_ELEMENT_FREE_USER_DATA, 0, string, stringBytes);
|
||||
EsButton *button = EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX | ES_ELEMENT_FREE_USER_DATA | ES_ELEMENT_STICKY_ACCESS_KEY, 0, string, stringBytes);
|
||||
button->userData = control;
|
||||
button->accessKey = accessKey;
|
||||
if (control->originalValueBool) EsButtonSetCheck(button, ES_CHECK_CHECKED, false);
|
||||
|
@ -327,8 +327,10 @@ void SettingsPageMouse(EsElement *element, SettingsPage *page) {
|
|||
table = EsPanelCreate(container, ES_CELL_H_FILL, &styleSettingsCheckboxGroup);
|
||||
SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsMouseSwapLeftAndRightButtons), 'B',
|
||||
"general", "swap_left_and_right_buttons", &api.global->swapLeftAndRightButtons);
|
||||
EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX, 0, INTERFACE_STRING(DesktopSettingsMouseShowShadow))->accessKey = 'W'; // TODO.
|
||||
EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX, 0, INTERFACE_STRING(DesktopSettingsMouseLocateCursorOnCtrl))->accessKey = 'L'; // TODO.
|
||||
SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsMouseShowShadow), 'W',
|
||||
"general", "show_cursor_shadow", nullptr); // TODO.
|
||||
SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsMouseLocateCursorOnCtrl), 'L',
|
||||
"general", "locate_cursor_on_ctrl", nullptr);
|
||||
|
||||
EsSpacerCreate(container, ES_CELL_H_FILL, ES_STYLE_BUTTON_GROUP_SEPARATOR);
|
||||
|
||||
|
@ -407,7 +409,7 @@ void SettingsButtonPressed(EsInstance *_instance, EsElement *element, EsCommand
|
|||
|
||||
{
|
||||
EsPanel *overlay = EsPanelCreate(stack, ES_CELL_H_LEFT | ES_CELL_V_TOP, &styleSettingsOverlayPanel);
|
||||
EsButton *backButton = EsButtonCreate(overlay, ES_CELL_H_LEFT | ES_BUTTON_TOOLBAR, 0, INTERFACE_STRING(DesktopSettingsBackButton));
|
||||
EsButton *backButton = EsButtonCreate(overlay, ES_CELL_H_LEFT | ES_BUTTON_TOOLBAR | ES_ELEMENT_STICKY_ACCESS_KEY, 0, INTERFACE_STRING(DesktopSettingsBackButton));
|
||||
backButton->accessKey = 'A';
|
||||
EsButtonSetIcon(backButton, ES_ICON_GO_HOME_SYMBOLIC);
|
||||
EsButtonOnCommand(backButton, SettingsBackButton);
|
||||
|
@ -443,7 +445,7 @@ void InstanceSettingsCreate(EsMessage *message) {
|
|||
}, nullptr);
|
||||
|
||||
for (uintptr_t i = 0; i < sizeof(settingsPages) / sizeof(settingsPages[0]); i++) {
|
||||
EsButton *button = EsButtonCreate(container, ES_ELEMENT_NO_FOCUS_ON_CLICK | ES_CELL_H_FILL,
|
||||
EsButton *button = EsButtonCreate(container, ES_ELEMENT_NO_FOCUS_ON_CLICK | ES_CELL_H_FILL | ES_ELEMENT_STICKY_ACCESS_KEY,
|
||||
&styleSettingsButton, settingsPages[i].string, settingsPages[i].stringBytes);
|
||||
button->userData = &settingsPages[i];
|
||||
button->accessKey = settingsPages[i].accessKey;
|
||||
|
|
|
@ -10,6 +10,7 @@ define ES_STYLE_BUTTON_GROUP_SEPARATOR (ES_STYLE_CAST(1233))
|
|||
define_private ES_STYLE_CANVAS_SHADOW (ES_STYLE_CAST(1451))
|
||||
define_private ES_STYLE_CHECKBOX_NORMAL (ES_STYLE_CAST(1559))
|
||||
define_private ES_STYLE_CHECKBOX_RADIOBOX (ES_STYLE_CAST(1567))
|
||||
define ES_STYLE_CLEAR_BACKGROUND (ES_STYLE_CAST(1597))
|
||||
define_private ES_STYLE_COLOR_CHOSEN_POINT (ES_STYLE_CAST(1241))
|
||||
define_private ES_STYLE_COLOR_CIRCLE (ES_STYLE_CAST(1243))
|
||||
define_private ES_STYLE_COLOR_HEX_TEXTBOX (ES_STYLE_CAST(1245))
|
||||
|
@ -17,6 +18,7 @@ define_private ES_STYLE_COLOR_PICKER_MAIN_PANEL (ES_STYLE_CAST(1247))
|
|||
define_private ES_STYLE_COLOR_SLIDER (ES_STYLE_CAST(1249))
|
||||
define_private ES_STYLE_CONTAINER_WINDOW_ACTIVE (ES_STYLE_CAST(1251))
|
||||
define_private ES_STYLE_CONTAINER_WINDOW_INACTIVE (ES_STYLE_CAST(1255))
|
||||
define_private ES_STYLE_CURSOR_LOCATOR (ES_STYLE_CAST(1591))
|
||||
define ES_STYLE_DIALOG_BUTTON_AREA (ES_STYLE_CAST(1259))
|
||||
define ES_STYLE_DIALOG_CONTENT (ES_STYLE_CAST(1261))
|
||||
define ES_STYLE_DIALOG_HEADING (ES_STYLE_CAST(1263))
|
||||
|
|
|
@ -123,6 +123,7 @@ struct WindowManager {
|
|||
// Miscellaneous:
|
||||
|
||||
EsRectangle workArea;
|
||||
bool inSingleCtrlPress;
|
||||
|
||||
// Game controllers:
|
||||
|
||||
|
@ -181,8 +182,6 @@ void SendMessageToWindow(Window *window, EsMessage *message) {
|
|||
return;
|
||||
}
|
||||
|
||||
window->lastEmbedKeyboardMessage.type = ES_MSG_INVALID;
|
||||
|
||||
if (message->type == ES_MSG_WINDOW_RESIZED) {
|
||||
message->windowResized.content = ES_RECT_4(0, window->width, 0, window->height);
|
||||
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
||||
|
@ -309,6 +308,17 @@ void WindowManager::PressKey(unsigned scancode) {
|
|||
KernelPanic("WindowManager::PressKey - Panic key pressed.\n");
|
||||
}
|
||||
|
||||
if (scancode == ES_SCANCODE_LEFT_CTRL) {
|
||||
inSingleCtrlPress = true;
|
||||
} else if (scancode == (ES_SCANCODE_LEFT_CTRL | K_SCANCODE_KEY_RELEASED) && inSingleCtrlPress) {
|
||||
EsMessage m;
|
||||
EsMemoryZero(&m, sizeof(EsMessage));
|
||||
m.type = ES_MSG_SINGLE_CTRL_PRESS;
|
||||
desktopProcess->messageQueue.SendMessage(nullptr, &m);
|
||||
} else {
|
||||
inSingleCtrlPress = false;
|
||||
}
|
||||
|
||||
if (eyedropping) {
|
||||
if (scancode == (ES_SCANCODE_ESCAPE | K_SCANCODE_KEY_RELEASED)) {
|
||||
EndEyedrop(true);
|
||||
|
|
|
@ -8,7 +8,6 @@ settings_path=0:/Settings
|
|||
default_user_documents_path=0:/
|
||||
installation_state=0
|
||||
click_chain_timeout_ms=500
|
||||
swap_left_and_right_buttons=0
|
||||
|
||||
[ui]
|
||||
; User interface settings that are accessible by all applications.
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue